Loading
chore(server): pre-shutdown delay component (S01 Step 4)
Summary
Introduces the S01 pre-shutdown delay component at internal/server/shutdown.go: a LabKit v2 app.Component that flips a readiness flag at the start of Shutdown, blocks for the configured delay (to let the kubelet → endpoints-controller propagation finish), and emits a structured WARN when the shared shutdown context preempts the wait. Leaf code — no callers on this branch. Step 5 (server.New) and Step 6 (composition root) will register the component on app.App and wire Check through httpserver.Server.AddReadinessCheck("shutdown", delay.Check).
- Plan: docs/plans/2026-05-20-s01-http-server-and-routing.md — Step 4
- Spec: docs/specs/S01-http-server-and-routing.md — sections "Shutdown sequencing" and AC #21 (closed)
- Issue: closes part of #89 (closed)
Acceptance criteria
| AC | Covered by |
|---|---|
#21 (closed) (preemption returns nil + single WARN with configured_delay/elapsed/error) |
TestShutdownDelay_Shutdown_TruncatedByCtxEmitsWarn |
The remaining 20 ACs from S01 are covered by other steps per the plan's coverage table.
Test plan
-
go test -race -short -count=1 ./internal/server/...— green -
golangci-lint v2.12.0— 0 issues -
goimports -l— clean - All 6 plan-listed tests present (
Name,Start_NoOp,Shutdown_BlocksForDelay,Shutdown_TruncatedByCtxEmitsWarn,Check_NilBeforeShutdown,Check_ErrorAfterShutdown) - Compile-time
app.Componentcontract assertion (var _ app.Component = (*ShutdownDelay)(nil)) - Polish tests pin three additional invariants:
TestNewShutdownDelay_NilLoggerPanics(constructor fail-fast),TestShutdownDelay_Shutdown_IdempotentOnSecondCall(Swap-based idempotency),TestShutdownDelay_ZeroDelay_NoBlockNoWarn(cross-layer contract pin)
Reviewability notes
- No callers yet. Leaf code; Step 5 instantiates and registers the component, Step 6 wires
delay.Checkto/-/readiness. The reviewer for Step 5 will see the constructor + type names locked in by this MR (any rename at that point is breaking). - WARN attribute keys are operator-facing contract.
configured_delay,elapsed, anderrorare documented in docs/dev/logging.md under "Component-specific keys" so the keys appear in spec/doc search. Renaming them post-merge breaks any downstream Kibana/Loki query. - Nil-logger fail-fast.
NewShutdownDelaypanics on a nil logger at boot rather than risking a nil-pointer panic on the SIGTERM truncation path. The composition root must inject a LabKit-built logger. - Idempotent Shutdown. A second
Shutdowncall short-circuits viaatomic.Bool.Swap, returning nil without re-blocking for the delay. LabKit'sapp.Appinvokes each component's Shutdown exactly once; the guard protects against operator-driven re-invocation (runbook scripts, retries). - Chore framing. This MR introduces no user-visible behavior until Step 6 wires the binary entry point at
cmd/artifact-registry/. The component sits unwired ininternal/server/after merge — hencechore(server)andtype::maintenancerather thanfeat/type::feature.
Related
- Step 1 (
internal/config): !254 (merged) — merged - Step 2 (
internal/transport): !261 (merged) — in review