Stop the filer test helpers from pinning gigabytes of log buffers (#10560)

* log buffer: wake the interval loop on shutdown instead of sleeping through it

loopInterval parked in time.Sleep(flushInterval) and only re-checked
IsStopping when it woke, so a buffer shut down early kept both loop
goroutines - and the PreviousBufferCount+1 slabs of BufferSize they
reach - alive for up to a full interval afterwards. Select on shutdownCh
against a ticker instead, and give the loops a WaitGroup so a test can
observe that they exit.

* test: release the filers the server tests build

Every helper here left its filer's meta log buffer running, so each test
pinned PreviousBufferCount+1 buffers of BufferSize for the rest of the
run: ~3.5GB of live heap across the package, which overruns the address
space on linux/386 and kills the 32-bit job with an out-of-memory throw.

Thread the test through the helpers so the buffer is shut down on
cleanup, and shut the subscribe harness's filer down outright - its
deletion loop keeps the whole filer reachable otherwise. That harness
quiesces its flush path first, since Filer.Shutdown closes the store a
flush still in flight would write through.
This commit is contained in:
Chris Lu
2026-08-04 11:38:38 -07:00
committed by GitHub
parent 5a5cd15054
commit f46b2a1925
13 changed files with 136 additions and 72 deletions
+14 -4
View File
@@ -193,7 +193,8 @@ type LogBuffer struct {
// Notified only when a flush lands, for readers that cannot act on an append
flushSubscribers map[string]chan struct{}
isStopping *atomic.Bool
shutdownCh chan struct{} // closed by ShutdownLogBuffer to wake blocked subscribers
shutdownCh chan struct{} // closed by ShutdownLogBuffer to wake blocked subscribers
loopsDone sync.WaitGroup // loopFlush and loopInterval signal exit
isAllFlushed bool
flushChan chan *dataToFlush
flushBudget *flushBudget
@@ -235,6 +236,7 @@ func NewLogBuffer(name string, flushInterval time.Duration, flushFn LogFlushFunc
},
}
lb.lastFlushedOffset.Store(-1) // Nothing flushed to disk yet
lb.loopsDone.Add(2)
go lb.loopFlush()
go lb.loopInterval()
return lb
@@ -740,6 +742,7 @@ func (logBuffer *LogBuffer) queueFlush(d *dataToFlush) bool {
}
func (logBuffer *LogBuffer) loopFlush() {
defer logBuffer.loopsDone.Done()
for d := range logBuffer.flushChan {
if d == nil {
break // shutdown sentinel
@@ -777,10 +780,17 @@ func (logBuffer *LogBuffer) loopFlush() {
}
func (logBuffer *LogBuffer) loopInterval() {
for !logBuffer.IsStopping() {
time.Sleep(logBuffer.flushInterval)
if logBuffer.IsStopping() {
defer logBuffer.loopsDone.Done()
// Wake on shutdown instead of sleeping through the interval: a goroutine
// parked in time.Sleep keeps the buffer and its ~40MB of slabs reachable
// for up to flushInterval after ShutdownLogBuffer.
ticker := time.NewTicker(logBuffer.flushInterval)
defer ticker.Stop()
for {
select {
case <-logBuffer.shutdownCh:
return
case <-ticker.C:
}
logBuffer.Lock()