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 authored and GitHub committed 2026-08-04 11:38:38 -07:00
1 parent 5a5cd15054
commit f46b2a1925
13 files changed
+136 -72

No files matched your search

+26 -1
View File
@@ -176,6 +176,11 @@ type subscribeHarness struct {
// stalls the metadata log flush" state the whole PR exists to handle.
gateMu sync.Mutex
flushGate chan struct{}
// flushMu guards stopped against the flushes still in flight when the test
// ends, so none of them writes through a store the cleanup has closed.
flushMu sync.RWMutex
stopped bool
}
const testFilerIdSuffix = "0000abcd"
@@ -221,7 +226,12 @@ func newSubscribeHarness(t *testing.T) *subscribeHarness {
// layer the way logFlushFunc writes through real volumes.
f.LocalMetaLogBuffer.ShutdownLogBuffer()
f.LocalMetaLogBuffer = log_buffer.NewLogBuffer("local", time.Minute, h.flushToStore, nil, nil)
t.Cleanup(f.LocalMetaLogBuffer.ShutdownLogBuffer)
// Shutting the buffer down is not enough: NewFiler's deletion loop keeps the
// filer - and so the replacement buffer's tens of megabytes - reachable for
// the rest of the run. Quiesce the flush path first, since Filer.Shutdown
// closes the store a flush still in flight would write through.
t.Cleanup(f.Shutdown)
t.Cleanup(h.stopFlushes)
h.fs = &FilerServer{
filer: f,
@@ -248,6 +258,15 @@ func (h *subscribeHarness) releaseFlushes() {
}
}
// stopFlushes lets go of any gated flush and waits for the ones in flight to
// finish, then refuses the rest, so the store stays untouched from here on.
func (h *subscribeHarness) stopFlushes() {
h.releaseFlushes()
h.flushMu.Lock()
defer h.flushMu.Unlock()
h.stopped = true
}
func (h *subscribeHarness) flushToStore(lb *log_buffer.LogBuffer, startTime, stopTime time.Time, buf []byte, minOffset, maxOffset int64) {
h.gateMu.Lock()
gate := h.flushGate
@@ -256,6 +275,12 @@ func (h *subscribeHarness) flushToStore(lb *log_buffer.LogBuffer, startTime, sto
<-gate
}
h.flushMu.RLock()
defer h.flushMu.RUnlock()
if h.stopped {
return
}
// The same file naming and append shape as logFlushFunc, against the fake
// volumes: one chunk per flushed window, named for the window start minute.
startTime, stopTime = startTime.UTC(), stopTime.UTC()