diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-16 03:16:42 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-16 03:16:42 +0200 |
| commit | 8790ff6f699d1b073d906b2752f714a0ed3ee2ae (patch) | |
| tree | 2961767af7db7ff538b40004b91d772882af3929 | |
| parent | a71136dc8c2a51dcaa49e4af5e42b3c6bffd6fa0 (diff) | |
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 <noreply@anthropic.com>
| -rw-r--r-- | internal/stats/stats.go | 8 |
1 files changed, 7 insertions, 1 deletions
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 } |
