summaryrefslogtreecommitdiff
path: root/internal/stats
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:16:42 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:16:42 +0200
commit8790ff6f699d1b073d906b2752f714a0ed3ee2ae (patch)
tree2961767af7db7ff538b40004b91d772882af3929 /internal/stats
parenta71136dc8c2a51dcaa49e4af5e42b3c6bffd6fa0 (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>
Diffstat (limited to 'internal/stats')
-rw-r--r--internal/stats/stats.go8
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
}