-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix data races in the logger module #1414
Conversation
3cf7530
to
bce07fd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see some locking being added which could be good, but would prefer seeing defer in use rather than manual unlock which might be easier to overlook on early returns.
Makefile
Outdated
@@ -93,7 +93,7 @@ install: $(wildcard *.go) | |||
build: $(EXECUTABLE) | |||
|
|||
$(EXECUTABLE): $(SOURCES) | |||
go build -i -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ | |||
go build -race -i -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this belongs here, but feel free to send another PR adding a "debug-build" makefile rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I am trying to figure out how to remove it from the commit.
modules/log/log.go
Outdated
@@ -212,13 +212,13 @@ func (l *Logger) SetLogger(adapter string, config string) error { | |||
// DelLogger removes a logger adapter instance. | |||
func (l *Logger) DelLogger(adapter string) error { | |||
l.lock.Lock() | |||
defer l.lock.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the defer responsible of a race condition ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Fixed.
You could just add a new commit and postpone cleanups to later
(when we're all happy you can use something like `git rebase -i origin/master`
and squash all commits togheter)
|
modules/log/log.go
Outdated
for _, l := range l.outputs { | ||
if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil { | ||
fmt.Println("ERROR, unable to WriteMsg:", err) | ||
} | ||
} | ||
l.lock.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace this with a defer on the line after the .Lock() call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is a bit of tricky and I am afraid it would cause dead-lock if the select statement is included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the case
block not a scope considered by defer
? Does -race also check for deadlocks ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expected defer is function scoped. Correct me if I am wrong.
I am not sure about deadlocks detection of -race. I suppose it doesn't help.
modules/log/log.go
Outdated
for _, l := range l.outputs { | ||
l.Flush() | ||
} | ||
l.lock.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace this with a defer on the line after the .Lock() call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
modules/log/log.go
Outdated
@@ -297,6 +302,7 @@ func (l *Logger) Close() { | |||
break | |||
} | |||
} | |||
l.lock.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace this with a defer on the line after the .Lock() call
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I have bug here as the lock encloses some synchronization points (is this the correct terminology?).
On Thu, Mar 30, 2017 at 04:07:49AM -0700, Mura Li wrote:
I expected defer is function scoped. Correct me if I am wrong.
It seems you're right about this
|
And locking the shared data structure accurately.
I took a closer look into the code and replaced the mutex with an r/w mutex. Also I think protecting the specific data tighter is better. A wrapper struct for the map might or might not worth the effort and complexity. But if syncmap becomes a thing. we could migrate our shared maps to using it. Edit: https://godoc.org/golang.org/x/sync/syncmap |
LGTM and you can try the syncmap in a separate PR maybe |
if _, ok := l.outputs[mode]; ok { | ||
l.lock.RLock() | ||
_, ok := l.outputs[mode] | ||
l.lock.RUnlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this cause a race between checking and removing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. Fixed.
@@ -174,7 +177,7 @@ type logMsg struct { | |||
// it can contain several providers and log message into all providers. | |||
type Logger struct { | |||
adapter string | |||
lock sync.Mutex | |||
lock sync.RWMutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the lock for the logger struct or for outputs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think It's for the outputs.
Normally, locks should be per data instead of a chunk of code. And I don't see any other fields except for 'outputs', would be updated concurrently. And channel variables should be thread-safe in its own right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point was, that reading the code, a lock called lock, with no comments, doesn't make it clear to me that it's a lock for outputs. I would add a comment, or name it outputsLock or something similar. But #1421 is also an option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. Typically the idiomatic way would be to wrap the map in a new type with its own lock. Now that we have syncmap, we can just use it.
In favor of #1421 |
Fixes #1412