Skip to content
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 deadlock when Stop and flush race #1430

Merged
merged 1 commit into from Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 12 additions & 11 deletions zapcore/buffered_write_syncer.go
Expand Up @@ -188,32 +188,33 @@ func (s *BufferedWriteSyncer) flushLoop() {
// Stop closes the buffer, cleans up background goroutines, and flushes
// remaining unwritten data.
func (s *BufferedWriteSyncer) Stop() (err error) {
var stopped bool

// Critical section.
func() {
stopped := func() bool {
s.mu.Lock()
defer s.mu.Unlock()

if !s.initialized {
return
return false
}

stopped = s.stopped
if stopped {
return
if s.stopped {
return false
}
s.stopped = true

s.ticker.Stop()
close(s.stop) // tell flushLoop to stop
<-s.done // and wait until it has
return true
}()

// Don't call Sync on consecutive Stops.
// Not initialized, or already stopped, no need for any cleanup.
if !stopped {
err = s.Sync()
return
}

return err
// Wait for flushLoop to end outside of the lock, as it may need the lock to complete.
// See https://github.com/uber-go/zap/issues/1428 for details.
<-s.done

return s.Sync()
}
8 changes: 8 additions & 0 deletions zapcore/buffered_write_syncer_test.go
Expand Up @@ -53,6 +53,14 @@ func TestBufferWriter(t *testing.T) {
assert.Equal(t, "foo", buf.String(), "Unexpected log string")
})

t.Run("stop race with flush", func(t *testing.T) {
buf := &bytes.Buffer{}
ws := &BufferedWriteSyncer{WS: AddSync(buf), FlushInterval: 1}
requireWriteWorks(t, ws)
assert.NoError(t, ws.Stop())
assert.Equal(t, "foo", buf.String(), "Unexpected log string")
})

t.Run("stop twice", func(t *testing.T) {
ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}}
_, err := ws.Write([]byte("foo"))
Expand Down