summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-29 23:35:50 +0300
committerPaul Buetow <paul@buetow.org>2026-04-29 23:35:50 +0300
commit7ddbf3291767905d42931608a75eca5be391fb07 (patch)
treed2784450b89261023177207b605b654fd7d39716 /internal
parent504fbf825bc153c9744c191f903c517e6dcad0d4 (diff)
fix: prevent GCWorker.Stop panic on nil ticker or double stop
Diffstat (limited to 'internal')
-rw-r--r--internal/service/gc.go12
-rw-r--r--internal/service/gc_test.go16
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()