Skip to content

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:

Worker and Options

A Worker is a single supervised background loop:

FieldTypePurpose
NamestringIdentifies the worker in logs and the probe error. Must be non-empty and unique within a Supervisor.
Runfunc(ctx context.Context) errorThe 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.

FieldTypeDefaultPurpose
Logger*slog.Loggerslog.Default()Receives restart and death events.
MaxRestartsint5Restart budget within a single unhealthy streak before permanent death.
BaseBackofftime.Duration1sFirst restart delay; doubles each restart.
MaxBackofftime.Duration30sCaps the exponential restart delay.
HealthyAftertime.Duration1mUptime beyond which a worker's restart budget resets.
JoinTimeouttime.Duration10sBounds 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 ctx is still live, logging every restart at WARN;
  • 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 ERROR and 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