From 3f80fe0b07c3e433ee54168f1e199c2ca9e1927a Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 12 May 2026 15:44:55 +0100 Subject: [PATCH] accounting: fix goroutine leak in ResetCounters ResetCounters unconditionally restarted the average loop, spawning a ticker goroutine that pinned the StatsInfo even when no loop had been running before. statsGroups.delete calls ResetCounters on every removed group, so deleting N stats groups leaked N goroutines and prevented GC of the underlying StatsInfo objects. Only restart the loop if it was active before the reset. (cherry picked from commit a8f102ce8f784633471391f291ee694aad7df919) --- fs/accounting/stats.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/accounting/stats.go b/fs/accounting/stats.go index 41d0462e3..8dc14f236 100644 --- a/fs/accounting/stats.go +++ b/fs/accounting/stats.go @@ -730,9 +730,15 @@ func (s *StatsInfo) ResetCounters() { s.startedTransfers = nil s.oldDuration = 0 + // Only restart the average loop if it was running. Otherwise + // ResetCounters would spawn a goroutine that pins the StatsInfo, + // leaking memory when called on stats that never started the loop. + wasStarted := s.average.started s._stopAverageLoop() s.average = averageValues{} - s._startAverageLoop() + if wasStarted { + s._startAverageLoop() + } } // ResetErrors sets the errors count to 0 and resets lastError, fatalError and retryError