diff --git a/cmd/silo/main.go b/cmd/silo/main.go index 213c0e8e..819ecd96 100644 --- a/cmd/silo/main.go +++ b/cmd/silo/main.go @@ -724,6 +724,12 @@ func main() { pluginService.OnLifecycleChange(appCtx) } + // backgroundInit collects non-critical startup work (catalog-size-dependent + // seeding, network-bound reconciliation) that must not block the HTTP + // listener. The steps run sequentially in a background goroutine once the + // server is ready to serve. Failures are logged, never fatal. + var backgroundInit []func(context.Context) + // Step 4b: Create metadata service and match worker (if needed). var metadataService *metadata.MetadataService var personRefreshService *metadata.PersonRefreshService @@ -830,23 +836,28 @@ func main() { matchWorker.SetMovieFileClaimer(movieQueueRepo) } if seriesQueueRepo != nil { - if cleaned, err := seriesQueueRepo.CleanupLegacySeriesGroupQueue(appCtx); err != nil { - slog.Warn("failed to clean legacy series group queue rows", "error", err) - } else if cleaned > 0 { - slog.Info("cleaned legacy series group queue rows", "count", cleaned) - } matchWorker.SetSeriesRootClaimer(seriesQueueRepo, cfg.Matcher.TVSeriesRootQueueEnabled()) + backgroundInit = append(backgroundInit, func(ctx context.Context) { + if cleaned, err := seriesQueueRepo.CleanupLegacySeriesGroupQueue(ctx); err != nil { + slog.Warn("failed to clean legacy series group queue rows", "error", err) + } else if cleaned > 0 { + slog.Info("cleaned legacy series group queue rows", "count", cleaned) + } + }) } if deps.FolderRepo != nil { - enabledFolders, err := deps.FolderRepo.GetEnabled(appCtx) - if err != nil { - slog.Warn("failed to seed metadata queues", "error", err) - } else { + backgroundInit = append(backgroundInit, func(ctx context.Context) { + start := time.Now() + enabledFolders, err := deps.FolderRepo.GetEnabled(ctx) + if err != nil { + slog.Warn("failed to seed metadata queues", "error", err) + return + } seedMovieQueue := func(folderID int) { if movieQueueRepo == nil { return } - if err := movieQueueRepo.SyncForFolder(appCtx, folderID); err != nil { + if err := movieQueueRepo.SyncForFolder(ctx, folderID); err != nil { slog.Warn("failed to seed movie match queue", "folder_id", folderID, "error", err) } } @@ -854,7 +865,7 @@ func main() { if seriesQueueRepo == nil { return } - if err := seriesQueueRepo.SyncForFolder(appCtx, folderID); err != nil { + if err := seriesQueueRepo.SyncForFolder(ctx, folderID); err != nil { slog.Warn("failed to seed series root queue", "folder_id", folderID, "error", err) } } @@ -872,7 +883,8 @@ func main() { seedMovieQueue(folder.ID) } } - } + slog.Info("deferred init: metadata match queues seeded", "folders", len(enabledFolders), "duration", time.Since(start)) + }) } deps.SkippedRootRepo = skippedRootRepo @@ -1011,9 +1023,11 @@ func main() { WithMatcher(historyimport.NewMatcher(historyRepo)). WithWatchState(watchstate.NewService(userStoreProvider).WithStableIdentityResolver(historyIdentity)). WithUserStoreProvider(userStoreProvider) - if err := watchProviderService.SweepOpenScrobbles(appCtx); err != nil { - slog.Warn("failed to sweep open watch provider scrobbles", "error", err) - } + 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() @@ -1653,6 +1667,22 @@ func main() { compatSrv.IdleTimeout = 120 * time.Second } + // Run non-critical startup work in the background so it doesn't delay the + // HTTP listener from accepting connections. Steps run sequentially and stop + // early if the app context is cancelled (shutdown). + if len(backgroundInit) > 0 { + go func() { + start := time.Now() + for _, step := range backgroundInit { + if appCtx.Err() != nil { + return + } + step(appCtx) + } + slog.Info("deferred startup init completed", "steps", len(backgroundInit), "duration", time.Since(start)) + }() + } + errCh := make(chan error, 2) go func() { slog.Info("HTTP server listening", "addr", cfg.Server.Listen)