-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #83295 from abarganier/backport22.1-79793
release-22.1: pkg/util/log: introduce log.BufferedSinkCloser
- Loading branch information
Showing
9 changed files
with
287 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Copyright 2022 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
package log | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/cockroachdb/cockroach/pkg/util/syncutil" | ||
"github.com/cockroachdb/errors" | ||
) | ||
|
||
// bufferedSinkCloser is a utility used by the logging system to | ||
// trigger the teardown of logging facilities gracefully within | ||
// CockroachDB. | ||
// | ||
// The API allows us to register buffered log sinks with RegisterBufferedSink | ||
// so we can wait for them to drain/timeout before exiting. | ||
// | ||
// The API also allows us to trigger the shutdown sequence via Close and | ||
// wait for all registered buffered log sinks to finish processing | ||
// before exiting, to help ensure a graceful shutdown of buffered | ||
// log sinks. | ||
type bufferedSinkCloser struct { | ||
// stopC is closed by Close() to signal all the registered sinks to shut down. | ||
stopC chan struct{} | ||
// wg is the WaitGroup used during the Close procedure to ensure that | ||
// all registered bufferedSink's have completed before returning. | ||
wg sync.WaitGroup | ||
mu struct { | ||
syncutil.Mutex | ||
// sinkRegistry acts as a set and stores references to all bufferSink's | ||
// registered with this bufferedSinkCloser instance. Only useful for debugging | ||
// purposes. | ||
sinkRegistry map[*bufferedSink]struct{} | ||
} | ||
} | ||
|
||
// newBufferedSinkCloser returns a new bufferedSinkCloser. | ||
func newBufferedSinkCloser() *bufferedSinkCloser { | ||
closer := &bufferedSinkCloser{ | ||
stopC: make(chan struct{}), | ||
} | ||
closer.mu.sinkRegistry = make(map[*bufferedSink]struct{}) | ||
return closer | ||
} | ||
|
||
// RegisterBufferedSink registers a bufferedSink with closer. closer.Close will | ||
// block for this sink's shutdown. | ||
// | ||
// A reference to the bufferSink is maintained in an internal registry to aid in | ||
// debug capabilities. | ||
// | ||
// Returns a channel that will be closed when closer.Close() is called. The | ||
// bufferedSink should listen to this channel and shutdown. The cleanup function | ||
// needs to be called once the bufferedSink has shutdown. | ||
func (closer *bufferedSinkCloser) RegisterBufferedSink( | ||
bs *bufferedSink, | ||
) (shutdown <-chan (struct{}), cleanup func()) { | ||
closer.mu.Lock() | ||
defer closer.mu.Unlock() | ||
|
||
if _, ok := closer.mu.sinkRegistry[bs]; ok { | ||
panic(errors.AssertionFailedf("buffered log sink registered more than once within log.bufferedSinkCloser: %T", bs.child)) | ||
} | ||
|
||
closer.mu.sinkRegistry[bs] = struct{}{} | ||
closer.wg.Add(1) | ||
return closer.stopC, func() { closer.bufferedSinkDone(bs) } | ||
} | ||
|
||
// bufferedSinkDone notifies the bufferedSinkCloser that one of the buffered | ||
// log sinks registered via RegisterBufferedSink has finished processing | ||
// & has terminated. | ||
func (closer *bufferedSinkCloser) bufferedSinkDone(bs *bufferedSink) { | ||
closer.mu.Lock() | ||
defer closer.mu.Unlock() | ||
// If we don't have the sink in the registry, then the sink is not accounted for | ||
// in the WaitGroup. Warn and return early - to signal the WaitGroup could prematurely | ||
// end the shutdown sequence of a different bufferSink that is registered. | ||
if _, ok := closer.mu.sinkRegistry[bs]; !ok { | ||
panic(errors.AssertionFailedf( | ||
"log shutdown sequence has detected an unregistered log sink: %T\n", bs.child)) | ||
} | ||
delete(closer.mu.sinkRegistry, bs) | ||
closer.wg.Done() | ||
} | ||
|
||
// defaultCloserTimeout is the default duration that | ||
// bufferedSinkCloser.Close(timeout) will wait for sinks to shut down. | ||
const defaultCloserTimeout = 90 * time.Second | ||
|
||
// Close triggers the logging shutdown process, signaling all registered sinks | ||
// to shut down and waiting for them to do so up to timeout. | ||
func (closer *bufferedSinkCloser) Close(timeout time.Duration) error { | ||
close(closer.stopC) | ||
doneCh := make(chan struct{}) | ||
go func() { | ||
closer.wg.Wait() | ||
doneCh <- struct{}{} | ||
}() | ||
|
||
select { | ||
case <-doneCh: | ||
return nil | ||
case <-time.After(timeout): | ||
closer.mu.Lock() | ||
defer closer.mu.Unlock() | ||
leakedSinks := make([]string, 0, len(closer.mu.sinkRegistry)) | ||
for bs := range closer.mu.sinkRegistry { | ||
leakedSinks = append(leakedSinks, fmt.Sprintf("%T", bs.child)) | ||
} | ||
return errors.Newf( | ||
"log shutdown sequence has detected a deadlock & has timed out. Hanging log sink(s): %v", | ||
leakedSinks) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright 2022 The Cockroach Authors. | ||
// | ||
// Use of this software is governed by the Business Source License | ||
// included in the file licenses/BSL.txt. | ||
// | ||
// As of the Change Date specified in that file, in accordance with | ||
// the Business Source License, use of this software will be governed | ||
// by the Apache License, Version 2.0, included in the file | ||
// licenses/APL.txt. | ||
|
||
package log | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestClose(t *testing.T) { | ||
t.Run("returns after all registered sinks exit", func(t *testing.T) { | ||
fakeSinkRoutine := func(stopC <-chan struct{}, bs *bufferedSink, cleanup func()) { | ||
defer cleanup() | ||
<-stopC | ||
} | ||
|
||
closer := newBufferedSinkCloser() | ||
for i := 0; i < 3; i++ { | ||
fakeBufferSink := &bufferedSink{} | ||
stopC, cleanup := closer.RegisterBufferedSink(fakeBufferSink) | ||
go fakeSinkRoutine(stopC, fakeBufferSink, cleanup) | ||
} | ||
|
||
require.NoError(t, closer.Close(1000*time.Hour) /* timeout - verify that it doesn't expire */) | ||
}) | ||
|
||
t.Run("times out if leaked bufferedSink doesn't shut down", func(t *testing.T) { | ||
closer := newBufferedSinkCloser() | ||
_, _ = closer.RegisterBufferedSink(&bufferedSink{}) | ||
require.Error(t, closer.Close(time.Nanosecond /* timeout */)) | ||
}) | ||
} | ||
|
||
func TestRegisterBufferSink(t *testing.T) { | ||
t.Run("registers bufferedSink as expected", func(t *testing.T) { | ||
lc := newBufferedSinkCloser() | ||
bs := &bufferedSink{} | ||
lc.RegisterBufferedSink(bs) | ||
lc.mu.Lock() | ||
defer lc.mu.Unlock() | ||
_, ok := lc.mu.sinkRegistry[bs] | ||
assert.True(t, ok) | ||
}) | ||
|
||
t.Run("panics if same bufferedSink registered twice", func(t *testing.T) { | ||
lc := newBufferedSinkCloser() | ||
bs := &bufferedSink{} | ||
lc.RegisterBufferedSink(bs) | ||
assert.Panics(t, | ||
func() { lc.RegisterBufferedSink(bs) }, | ||
"expected RegisterBufferedSink() to panic when same sink registered twice.") | ||
}) | ||
} | ||
|
||
func TestBufferSinkDone(t *testing.T) { | ||
t.Run("signals waitgroup and removes bufferSink from registry", func(t *testing.T) { | ||
closer := newBufferedSinkCloser() | ||
bs := &bufferedSink{} | ||
|
||
closer.RegisterBufferedSink(bs) | ||
|
||
closer.mu.Lock() | ||
assert.Len(t, closer.mu.sinkRegistry, 1, "expected sink registry to include registered bufferedSink") | ||
closer.mu.Unlock() | ||
|
||
closer.bufferedSinkDone(bs) | ||
|
||
closer.mu.Lock() | ||
assert.Empty(t, closer.mu.sinkRegistry, "expected sink registry to be empty") | ||
closer.mu.Unlock() | ||
|
||
require.NoError(t, closer.Close(time.Second /* timeout */), "bufferedSinkCloser timed out unexpectedly") | ||
}) | ||
|
||
t.Run("panics if called on unregistered bufferSink", func(t *testing.T) { | ||
closer := newBufferedSinkCloser() | ||
bs1 := &bufferedSink{} | ||
bs2 := &bufferedSink{} | ||
|
||
closer.RegisterBufferedSink(bs1) | ||
|
||
closer.mu.Lock() | ||
_, ok := closer.mu.sinkRegistry[bs1] | ||
assert.Len(t, closer.mu.sinkRegistry, 1, "length of bufferSink registry larger than expected") | ||
assert.True(t, ok, "expected bufferSink to be in registry") | ||
closer.mu.Unlock() | ||
|
||
require.Panics(t, func() { | ||
closer.bufferedSinkDone(bs2) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.