refactor(server): make background jobs restartable (1/2) - #776
refactor(server): make background jobs restartable (1/2)#776ankitgoswami wants to merge 3 commits into
Conversation
🔐 Codex Security Review
Review SummaryOverall Risk: MEDIUM Findings[MEDIUM] Telemetry restart can permanently strand devices in in-flight state
NotesThe reviewed diff is primarily lifecycle/shutdown work for background jobs. I did not find auth, SQL injection, command injection, protobuf wire-format, or cryptostealing/pool-hijack issues in the changed hunks. Generated by Codex Security Review | |
ba2539e to
82ffdf3
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82ffdf3e04
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR introduces a shared restartable lifecycle contract for long-running Fleet server background work, and migrates several domain services to implement that contract so they can be started/stopped safely across active/passive transitions (while preserving current standalone fleetd behavior).
Changes:
- Adds
server/internal/runtimejobswithLifecycle, validated namedJob, and orderedGroupstart/stop orchestration (including rollback-on-partial-start and terminal failure semantics). - Refactors multiple background services (command execution, telemetry, scheduling, IP scanning, diagnostics, curtailment loops) to support
Start -> Stop -> Startwith activation-scoped cancellation/drain tracking. - Hardens
fleetdshutdown behavior with a consistent “graceful budget then bounded drain” stop helper while keeping current startup wiring intact ahead of the catalog cutover in PR B.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| server/internal/testutil/service_provider.go | Updates execution service construction to new restartable lifecycle API. |
| server/internal/runtimejobs/group.go | Adds lifecycle orchestration primitives (Job/Group) for restartable background work. |
| server/internal/runtimejobs/group_test.go | Adds coverage for ordered start/reverse stop, rollback, deadlines, and terminal failure behavior. |
| server/internal/handlers/errorquery/handler_test.go | Updates diagnostics service construction API usage. |
| server/internal/domain/telemetry/service.go | Refactors telemetry service to implement restartable lifecycle with activation-scoped cancellation/drain. |
| server/internal/domain/telemetry/service_test.go | Adds/updates tests for telemetry restart behavior and removes timing-based flakiness in channel reads. |
| server/internal/domain/schedule/processor.go | Refactors scheduler processor lifecycle to support restartable start/stop with bounded drain semantics. |
| server/internal/domain/schedule/processor_test.go | Updates stop signature usage and adds tests for restartability and deadline-bounded stop. |
| server/internal/domain/ipscanner/service.go | Refactors IP scanner to use per-activation state and implement restartable lifecycle. |
| server/internal/domain/ipscanner/service_test.go | Adds tests for stop/start behavior, deadline handling, and cancellation-unblocking for worker drains. |
| server/internal/domain/ipscanner/integration_test.go | Updates stop signature usage for the new lifecycle. |
| server/internal/domain/diagnostics/watcher_test.go | Updates diagnostics service construction API usage. |
| server/internal/domain/diagnostics/service.go | Makes diagnostics construction side-effect free and adds explicit lifecycle start/stop for the closer goroutine. |
| server/internal/domain/diagnostics/service_test.go | Updates diagnostics service construction API usage. |
| server/internal/domain/diagnostics/closer_test.go | Updates closer tests for explicit Start/Stop and adds restart test coverage. |
| server/internal/domain/curtailment/reconciler/reconciler.go | Refactors reconciler to implement restartable lifecycle with activation ownership and deadline-bounded stop. |
| server/internal/domain/curtailment/reconciler/reconciler_test.go | Updates tests for new lifecycle and adds restart/timeout ownership scenarios. |
| server/internal/domain/curtailment/mqttingest/subscriber.go | Refactors subscriber to implement restartable lifecycle with “timed-out stop retains ownership” semantics. |
| server/internal/domain/curtailment/mqttingest/subscriber_test.go | Updates tests for new Stop signature and adds timeout ownership coverage. |
| server/internal/domain/curtailment/alert_metrics.go | Refactors alert metrics loop to implement restartable lifecycle with deadline-bounded stop. |
| server/internal/domain/curtailment/alert_metrics_test.go | Updates tests and adds coverage for activation cancellation + stop-timeout ownership. |
| server/internal/domain/command/zero_target_integration_test.go | Updates execution service construction API usage. |
| server/internal/domain/command/reaper_integration_test.go | Updates execution service construction API usage. |
| server/internal/domain/command/execution_service.go | Refactors command execution service to implement restartable lifecycle with activation-scoped cancellation/drain. |
| server/internal/domain/command/execution_service_test.go | Updates tests for new construction and adds restart/timeout/drain ownership coverage. |
| server/internal/domain/command/execution_service_credentials_test.go | Updates execution service construction API usage. |
| server/cmd/fleetd/main.go | Updates wiring for explicit diagnostics closer lifecycle and standardized shutdown for restartable jobs. |
82ffdf3 to
b16a5f2
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b16a5f21c6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- isolate background telemetry results per activation\n- preserve request-owned refresh results across restart\n- cover stale status and metrics result isolation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50ec2128ec
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| s.runWG.Go(func() { s.statusWriterRoutineForActivation(ctx, results.status) }) | ||
| s.runWG.Go(func() { s.metricsWriterRoutineForActivation(ctx, results.metrics) }) |
There was a problem hiding this comment.
Keep telemetry writers alive until workers drain
In the current per-activation implementation, these writer goroutines are canceled at the same time as the worker goroutines registered above, so during Stop a writer can take the ctx.Done() branch and exit while a miner RPC worker is still finishing. That worker can then enqueue its final status/metrics into the buffered per-run channel, but no writer remains and Stop still returns nil after the worker exits, silently dropping a completed poll during HA demotion/restart or shutdown. Keep the writers running until workers are done, or close/drain the per-run result channels after worker completion.
Useful? React with 👍 / 👎.
|
🤖 Superseded by the smaller runtime-jobs PR tree:
#788 is temporarily based on |
Reviewable diff: +921/-300 across 11 files (excludes generated, test, and story files).
Summary
Fleet's long-running server work now shares one restartable lifecycle contract. This gives the upcoming HA supervisor a safe boundary for stopping active work on demotion and starting it again on activation without rebuilding the process. Standalone
fleetdbehavior remains intact while the jobs are prepared for catalog-driven startup.Stack:
fleetdstartup/shutdown over to the runtime groupJob selection, configuration filtering, passive-mode coordinator wiring, and epoch fencing are intentionally outside this PR. Catalog policy and construction land in PR B; coordinator behavior follows the catalog cutover.
How it works
Restartable background work implements
Lifecycle:Start(context.Context) erroractivates it, whileStop(context.Context) errorcancels and drains that activation within the caller's budget. AJobadds the validated runtime name, delegates that lifecycle, and itself implementsLifecycle; services do not need a second "named job" abstraction.The runtime group receives an ordered list of jobs, validates unique names, and starts them in order. If startup fails partway through, it cancels the activation and rolls back the successfully started prefix in reverse order. Normal shutdown follows the same cancel-then-reverse-stop path. Rollback and shutdown each use one group-wide cleanup deadline, capped by any earlier caller deadline; incomplete cleanup permanently blocks reactivation so a new owner cannot overlap surviving work.
Command execution, telemetry, scheduling, IP scanning, diagnostics, MQTT ingest, alert metrics, and curtailment reconciliation now expose the same lifecycle shape and keep fresh cancellation/drain state per activation. Their stop paths prevent new work, wait for admitted goroutines, honor cancellation, and permit
Start -> Stop -> Startafter a clean drain. The diagnostics constructor is side-effect free, butfleetdexplicitly starts the closer in this PR so stale errors continue to close before PR B lands.Standalone shutdown gives jobs the existing 10-second graceful budget followed, when needed, by one additional 10-second drain window after forced cancellation. Teardown continues if a dependency still does not drain, so one stuck job cannot block process shutdown indefinitely; MQTT and diagnostics retain their already-bounded teardown behavior.
flowchart TB C["Catalog and scope selection (PR B)"] --> J["Validated named Job"] L["Concrete Lifecycle"] --> J J --> G["Runtime Group"] G --> S["Start jobs in order"] S --> A["Activation-scoped workers"] A --> X["Cancel activation"] X --> R["Stop jobs in reverse order"] R --> D["One group-wide cleanup deadline"] D --> OK["Stopped and restartable"] D --> F["Cleanup failure blocks reactivation"]stateDiagram-v2 [*] --> Stopped Stopped --> Starting: Start Starting --> Running: all jobs started Starting --> Stopped: rollback completed Starting --> Failed: rollback incomplete Running --> Stopped: stop completed Running --> Failed: stop incomplete Failed --> Failed: Start rejected with cleanup causeAreas of the code involved
server/internal/runtimejobs/(new)Lifecycle, validated namedJob, and orderedGroupserver/internal/domain/command/,server/internal/testutil/server/internal/domain/telemetry/server/internal/domain/schedule/server/internal/domain/ipscanner/server/internal/domain/curtailment/server/internal/domain/diagnostics/server/cmd/fleetd/main.goKey technical decisions & trade-offs
Jobowns its name and validation while implementing and delegatingLifecycle; concrete services remain unaware of runtime catalog naming.Related
Related: #740
Testing & validation
go test -short -race -count=1acrossruntimejobs, every hardened domain package, andcmd/fleetdgo test -short -run '^$' ./...fromserver/to compile every server packagejust _lint-server(golangci-lint: 0 issues)Coordinator transitions, epoch fencing, passive request gating, and the final 12-job catalog are not exercised here; those remain PR B and HA follow-up work.
Post-Deploy Monitoring & Validation
No migration or configuration change is required. After deployment, verify that command execution, telemetry, scheduling, scanning, diagnostics, MQTT ingest, and curtailment loops start normally and that shutdown logs contain no
runtime job exceeded shutdown timeoutorfailed to drain runtime joberrors. Rollback is the previous server artifact; this PR introduces no persisted state.