From 7ddbf3291767905d42931608a75eca5be391fb07 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 29 Apr 2026 23:35:50 +0300 Subject: fix: prevent GCWorker.Stop panic on nil ticker or double stop --- internal/service/gc.go | 12 ++++++++---- internal/service/gc_test.go | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/internal/service/gc.go b/internal/service/gc.go index fd259f3..5ac4120 100644 --- a/internal/service/gc.go +++ b/internal/service/gc.go @@ -22,6 +22,7 @@ type GCWorker struct { logger *slog.Logger ticker *time.Ticker stopCh chan struct{} + stopOnce sync.Once wg sync.WaitGroup mediaRoot string } @@ -69,11 +70,14 @@ func (w *GCWorker) Start() { } // Stop stops the GC goroutine and waits for it to finish. +// Safe to call multiple times or before Start() (idempotent, no-op). func (w *GCWorker) Stop() { - if w.ticker != nil { - w.ticker.Stop() - } - close(w.stopCh) + w.stopOnce.Do(func() { + if w.ticker != nil { + w.ticker.Stop() + } + close(w.stopCh) + }) w.wg.Wait() } diff --git a/internal/service/gc_test.go b/internal/service/gc_test.go index db9d517..84402cc 100644 --- a/internal/service/gc_test.go +++ b/internal/service/gc_test.go @@ -113,6 +113,22 @@ func TestGCWorker_ListDeletedError(t *testing.T) { w.RunOnce() } +func TestGCWorker_StopBeforeStart(t *testing.T) { + now := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + w := NewGCWorker(&repository.MockStore{}, &clock.MockClock{T: now}, "/tmp", time.Minute, logger) + w.Stop() // must not panic +} + +func TestGCWorker_DoubleStop(t *testing.T) { + now := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + w := NewGCWorker(&repository.MockStore{}, &clock.MockClock{T: now}, "/tmp", time.Minute, logger) + w.Start() + w.Stop() + w.Stop() // must not panic +} + func TestGCWorker_RelPathFallback(t *testing.T) { now := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) tmpDir := t.TempDir() -- cgit v1.2.3