From 8790ff6f699d1b073d906b2752f714a0ed3ee2ae Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 16 Mar 2026 03:16:42 +0200 Subject: Fix time.After timer leak in stats.acquireFileLock Replace per-iteration time.After with a single time.NewTimer that is reused via Reset() and cleaned up with defer Stop(). Prevents leaking a timer and channel on every retry iteration. Co-Authored-By: Claude Opus 4.6 --- internal/stats/stats.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'internal/stats/stats.go') diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 7fa61b0..95981c5 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -151,18 +151,24 @@ func Update(ctx context.Context, provider, model string, sentBytes, recvBytes in return nil } +// acquireFileLock spins on tryLockFile until it succeeds, the context is +// cancelled, or an unexpected error occurs. A single timer is reused across +// retries to avoid leaking timers/channels on every loop iteration. func acquireFileLock(ctx context.Context, f *os.File) (func() error, error) { fd := f.Fd() + retryTimer := time.NewTimer(5 * time.Millisecond) + defer retryTimer.Stop() for { err := tryLockFile(fd) if err == nil { return func() error { return unlockFile(fd) }, nil } if errors.Is(err, errLockWouldBlock) { + retryTimer.Reset(5 * time.Millisecond) select { case <-ctx.Done(): return nil, ctx.Err() - case <-time.After(5 * time.Millisecond): + case <-retryTimer.C: } continue } -- cgit v1.2.3