Stream beets imports over HTTP.

feat(server): cancel previews and unstarted queued imports on shutdown #28

Merged glerp.sullen.net
targeting main from glerp.sullen.net/goosefoot:feat/shutdown-cancellation
Merged opened by and assigned to glerp.sullen.net
targeting main from glerp.sullen.net/goosefoot:feat/shutdown-cancellation

Summary #

Cancel in-flight dry_run: true previews and queued-but-unstarted dry_run: false imports immediately on server shutdown (SIGTERM / SIGINT) while preserving live SSE streams for an already-running import during the graceful ShutdownTimeout drain window, and deterministically reap stuck subprocess groups with an SSE error event if ShutdownTimeout expires.

Changes #

  • Two-phase shutdown contexts & subprocess drain (internal/server/server.go, cmd/goosefoot/main.go):
    • Add ShutdownTimeout (DefaultShutdownTimeout = 15s) to server.Config.
    • Keep r.Context() strictly scoped to client disconnects (do not wire ListenAndServe's ctx into httpServer.BaseContext), preventing an actively running import from prematurely detaching its SSE stream before delivering event: complete.
    • Trigger shutdownContext immediately on <-ctx.Done() before httpServer.Shutdown(graceContext).
    • If ShutdownTimeout expires, trigger forcedShutdownContext and drain httpServer.Shutdown (bounded by SSEWriteTimeout) so cmdstream.Run finishes delivering SIGKILL to any Setpgid child process group, reaping it via cmd.Wait(), and flushing the final SSE error event before ListenAndServe returns to main().
  • Preview, queue, import & hook cancellation (internal/server/pipeline.go, internal/server/import.go):
    • Hook shutdownContext into runPreview via context.AfterFunc, flushing SSE event: error ("step":"preview", "error":"server shutting down") when aborted by server shutdown.
    • Abort unstarted queued imports in acquireQueue on shutdownContext (including simultaneous-wakeup recheck and queue token release), flushing SSE event: error ("step":"queued", "error":"server shutting down") without starting new filesystem or SQLite mutations during shutdown.
    • Normalize forced-shutdown interruption in runImportPipeline and runPostImportHooks so forced cancellation during importing or post-import hooks stops remaining hooks, suppresses event: complete, and emits SSE event: error naming the interrupted step with "error":"server shutting down" (while keeping non-shutdown hook failures non-fatal).
  • Tests (internal/server/server_test.go):
    • Behavioral ListenAndServe tests covering immediate dry_run preview cancellation, queued import abortion alongside active import + hook completion (event: complete), forced-shutdown importing cancellation, forced-shutdown hook process-group reaping + remaining-hook suppression, and grace-deadline expiry right before hooks start.
Commits 0