Skip to content

Commit

Permalink
log: SyncLogger: defer mutex unlocks for panic safety (#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbourgon committed Apr 12, 2020
1 parent cb67d82 commit e10c4c8
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 9 deletions.
15 changes: 6 additions & 9 deletions log/sync.go
Expand Up @@ -65,9 +65,8 @@ type syncWriter struct {
// progress, the calling goroutine blocks until the syncWriter is available.
func (w *syncWriter) Write(p []byte) (n int, err error) {
w.Lock()
n, err = w.Writer.Write(p)
w.Unlock()
return n, err
defer w.Unlock()
return w.Writer.Write(p)
}

// fdWriter is an io.Writer that also has an Fd method. The most common
Expand All @@ -87,9 +86,8 @@ type fdSyncWriter struct {
// progress, the calling goroutine blocks until the fdSyncWriter is available.
func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
w.Lock()
n, err = w.fdWriter.Write(p)
w.Unlock()
return n, err
defer w.Unlock()
return w.fdWriter.Write(p)
}

// syncLogger provides concurrent safe logging for another Logger.
Expand All @@ -110,7 +108,6 @@ func NewSyncLogger(logger Logger) Logger {
// progress, the calling goroutine blocks until the syncLogger is available.
func (l *syncLogger) Log(keyvals ...interface{}) error {
l.mu.Lock()
err := l.logger.Log(keyvals...)
l.mu.Unlock()
return err
defer l.mu.Unlock()
return l.logger.Log(keyvals...)
}
18 changes: 18 additions & 0 deletions log/sync_test.go
Expand Up @@ -81,3 +81,21 @@ func TestSyncWriterFd(t *testing.T) {
t.Error("NewSyncWriter does not pass through Fd method")
}
}

func TestSyncLoggerPanic(t *testing.T) {
var logger log.Logger
logger = log.LoggerFunc(func(...interface{}) error { panic("!") })
logger = log.NewSyncLogger(logger)

f := func() {
defer func() {
if x := recover(); x != nil {
t.Log(x)
}
}()
logger.Log("hello", "world")
}

f()
f() // without defer Unlock, this one can deadlock
}

2 comments on commit e10c4c8

@Tiny-Paws
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get how it would deadlock without using defer. Can someone please enlighten me ?

@ChrisHines
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defer will get called even if the wrapped logger panics. Without the defer the SyncLogger.Log method would exit without unlocking the mutex and all future calls would deadlock. https://golang.org/ref/spec#Defer_statements

Please sign in to comment.