From 62563f5c808b767c53f7eded5dd180e1743cf017 Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Thu, 28 May 2026 20:26:15 -0400 Subject: [PATCH] fix(startup): sweep open scrobbles before accepting playback The open-scrobble sweep was queued in the deferred background-init list, which runs concurrently with the HTTP listener; a resume immediately after restart could start new scrobbles before the previous process's open sessions were stopped, leaving overlapping/stale scrobbles on remote providers. Run the sweep synchronously before the listener starts, bounded by a 30s timeout so an unreachable provider can't hang startup (the heavier non-critical init stays deferred). Addresses PR #21 review (P2). Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/silo/main.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/silo/main.go b/cmd/silo/main.go index 819ecd96..78a4bd9e 100644 --- a/cmd/silo/main.go +++ b/cmd/silo/main.go @@ -1023,11 +1023,6 @@ func main() { WithMatcher(historyimport.NewMatcher(historyRepo)). WithWatchState(watchstate.NewService(userStoreProvider).WithStableIdentityResolver(historyIdentity)). WithUserStoreProvider(userStoreProvider) - backgroundInit = append(backgroundInit, func(ctx context.Context) { - if err := watchProviderService.SweepOpenScrobbles(ctx); err != nil { - slog.Warn("failed to sweep open watch provider scrobbles", "error", err) - } - }) } deps.SessionMgr = sessionMgr deps.PlaybackRealtimeHub = playback.NewRealtimeHub() @@ -1683,6 +1678,19 @@ func main() { }() } + // Stop scrobble sessions left open by the previous process BEFORE the + // listener accepts playback requests, so a resume immediately after a + // restart doesn't create overlapping/stale scrobbles on remote providers. + // Bounded by a timeout so an unreachable provider can't hang startup (which + // is why the rest of non-critical init stays deferred below). + if watchProviderService != nil { + sweepCtx, cancelSweep := context.WithTimeout(appCtx, 30*time.Second) + if err := watchProviderService.SweepOpenScrobbles(sweepCtx); err != nil { + slog.Warn("failed to sweep open watch provider scrobbles", "error", err) + } + cancelSweep() + } + errCh := make(chan error, 2) go func() { slog.Info("HTTP server listening", "addr", cfg.Server.Listen)