diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-29 23:35:50 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-29 23:35:50 +0300 |
| commit | 7ddbf3291767905d42931608a75eca5be391fb07 (patch) | |
| tree | d2784450b89261023177207b605b654fd7d39716 | |
| parent | 504fbf825bc153c9744c191f903c517e6dcad0d4 (diff) | |
fix: prevent GCWorker.Stop panic on nil ticker or double stop
| -rw-r--r-- | internal/service/gc.go | 12 | ||||
| -rw-r--r-- | 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() |
