Appearance
internal/platform/worker
internal/platform/worker supervises long-running background loops so a worker that crashes cannot stay silently dead for the process lifetime. It replaces the historical "bare go func() with a warn-on-exit wrapper" pattern, where a loop that returned a non-context error logged once and then never ran again, invisible to /readyz.
This document is the authoritative reference for:
See also:
- docs/reference/platform/db.md — the shared pool the same composition root constructs once and threads through every surface.
- docs/operations/failure-modes.md — the boot-gate-then-degrade readiness model a permanently-dead worker feeds into.
Worker and Options
A Worker is a single supervised background loop:
| Field | Type | Purpose |
|---|---|---|
Name | string | Identifies the worker in logs and the probe error. Must be non-empty and unique within a Supervisor. |
Run | func(ctx context.Context) error | The loop. It MUST return promptly once ctx is cancelled. A non-context error, a nil return while ctx is still live, or a panic is treated as a crash. |
Options tune a Supervisor; the zero value is usable because New fills every field with its documented default.
| Field | Type | Default | Purpose |
|---|---|---|---|
Logger | *slog.Logger | slog.Default() | Receives restart and death events. |
MaxRestarts | int | 5 | Restart budget within a single unhealthy streak before permanent death. |
BaseBackoff | time.Duration | 1s | First restart delay; doubles each restart. |
MaxBackoff | time.Duration | 30s | Caps the exponential restart delay. |
HealthyAfter | time.Duration | 1m | Uptime beyond which a worker's restart budget resets. |
JoinTimeout | time.Duration | 10s | Bounds Wait's post-cancel join. |
Supervision behaviour
Register adds workers before Start; it rejects an empty name, a nil Run, a duplicate name, or registration after Start with a matching sentinel (ErrEmptyName, ErrNilRun, ErrDuplicateName, ErrAlreadyStarted).
Start(ctx) launches each worker in its own supervised goroutine and returns immediately. For each worker the supervisor:
- restarts the loop with exponential backoff plus equal jitter when it exits while
ctxis still live, logging every restart atWARN; - resets the restart budget once the loop has run longer than
HealthyAfter, so a rare transient crash does not accumulate toward permanent death; - marks the worker permanently dead after the restart budget is exhausted, logging at
ERRORand flipping the readiness probe red; - recovers panics — logging the value and stack — and treats them as crashes so one worker cannot abort the process.
go
sup := worker.New(worker.Options{Logger: logger})
if err := sup.Register(worker.Worker{Name: "endpoint-sweeper", Run: sweep}); err != nil {
return err
}
if err := sup.Start(ctx); err != nil {
return err
}Readiness probe
Supervisor.HealthProbe matches internal/platform/health's ProbeFunc signature without importing the health package. It returns nil while every worker is running or restarting, and a non-nil error naming each permanently-dead worker once one exhausts its budget — so /readyz flips red instead of a dead sweeper degrading the replica silently.
go
registry.Register("workers", sup.HealthProbe)Bounded shutdown join
Wait(ctx) blocks until every supervised goroutine has returned or the join deadline fires, whichever comes first. The composition root cancels the workers' context and then calls Wait, giving a worker mid-iteration JoinTimeout to unwind before Run is abandoned. It returns ErrJoinTimeout when the deadline fires with goroutines still running, or the caller's context error when that context is cancelled first, so shutdown neither abandons a sweep mid-flight nor hangs forever on a worker that ignores cancellation.
Cross-references
../../contributing/layout.md— the bounded-context map locating this package in the codebase.../../../internal/platform/worker/— the package source.../index.md— the Reference quadrant index.