Supervisor Type
Keeps a `Command` alive: runs it, classifies every exit against the `RestartPolicy` and the `StopWhen` predicate, and restarts it after an exponential-backoff delay until supervision ends. `Command.Retry` answers "run this once, replaying on failure"; a supervisor answers the different question **"keep this alive"** — a minimal `runit`/`systemd`-style keeper on top of the runner layer. The two are distinct layers: a supervised command's own `Retry` is **not** applied per incarnation (supervision runs the bare runner), so use the supervisor's own restart policy and backoff instead. Runs go through an `IProcessRunner` (the default `JobRunner`); override with `WithRunner` to share a `ProcessGroup` or inject a test double. Defaults: `OnCrash`, unlimited restarts, backoff `200ms × 2.0` capped at 30 s, jitter on, failure-storm guard off (enable with `StormPause`; failure half-life 30 s, threshold 5.0). **Observability while supervision runs.** `RunAsync` only reports its `SupervisionOutcome` at the very end, which is unusable for a long-lived (potentially never-ending) supervised service — so two callback seams, `OnRestart` and `OnStormPause`, report restarts and storm pauses *live*, as they happen (e.g. for a health check or crash-loop alerting). Both callbacks are invoked synchronously, from the supervision loop itself (the same async context driving `RunAsync`), right before the corresponding delay is slept out — so a slow or blocking handler delays every restart/pause; keep handlers quick and non-blocking. Neither callback changes `SupervisionOutcome`'s semantics — `Restarts`/`StormPauses`/`Stopped` are unaffected and remain the authoritative final tally; the callbacks are an additive, best-effort live view.
Constructors
| Constructor |
Description
|
|
Supervise `command` with the default `JobRunner` (a fresh private kill-on-drop group per incarnation).
|
Instance members
| Instance member |
Description
|
|
Exponential backoff before each restart: the delay is `base × factor^n`, capped by `MaxBackoff`, where `n` is an escalation exponent that climbs by one per restart but **resets to 0 after a healthy incarnation** (one that stayed up at least as long as `MaxBackoff` and wasn't a hang killed by its timeout) — so a long-lived service that crashes occasionally restarts promptly instead of being pinned at the ceiling. `n` is not the lifetime restart count (`SupervisionOutcome.Restarts`). A `factor` below `1.0` (or non-finite) is treated as `1.0`. Default: `200ms × 2.0`.
|
|
Bound (or widen) the output captured from each incarnation. The default is a bounded tail; pass `OutputBufferPolicy.Unbounded` to retain everything.
|
|
Half-life of the failure score used by the storm guard (default: 30 s). A zero half-life keeps no history. No effect unless `StormPause` is set.
|
|
Failure score above which the storm guard trips (default: `5.0`). A non-finite threshold never trips. No effect unless `StormPause` is set.
|
|
Classify a crash — or a spawn/IO failure that never produced a result — as *permanent*, so the supervisor gives up instead of restarting it forever. `classifier` receives the `ProcessError` of the failed incarnation: for a crashed run (one that produced a `ProcessResult` but is not a success) that is the crash's own `ProcessResult.FailureError` projection; for a run that never produced a result at all, it is the runner's own error. This is a different seam than `StopWhen`, which classifies by *outcome* (`ProcessResult`) — `GiveUpWhen` classifies by *error kind*, independent of whether the incarnation ever ran. Not checked for a clean exit, nor for a run `StopWhen` already ended, nor for a crash the `RestartPolicy` itself would not have restarted (e.g. under `Never`) — those already stop supervision with a more specific reason. When checked, it runs *before* `MaxRestarts`: a permanent-failure verdict wins over "budget not yet exhausted". A crashed match reports `StopReason.GaveUp`; a match on a run that never produced a result has no result to report and surfaces the classified error directly as `RunAsync`'s `Error`, same as an exhausted budget on that path. Default: unset — a permanent failure restarts forever (throttled only by backoff/`MaxRestarts`/the storm guard), matching the prior behavior.
|
|
Multiply each backoff delay by a uniform factor in `[0.5, 1.5)` (default: **on**), so a fleet of supervised workers restarted by the same incident does not stampede back in lockstep. Disable for deterministic delays.
|
|
|
|
Restart at most `count` times — `count + 1` total runs (default: unlimited). `count` must be non-negative (`0` means no restarts at all — a single run; a negative value is rejected with `ArgumentOutOfRangeException`).
|
|
Observe restarts live: `handler` runs synchronously, from the supervision loop, right before each restart's backoff delay is slept out — after the failed/finished incarnation, before the next one starts. Invoked on every restart (a crash, a timeout, or a retried transient runner error), never for the initial run. `handler` runs on the same async context driving `RunAsync`, so keep it quick and non-blocking — a slow handler delays every restart. Purely additive: does not change `SupervisionOutcome.Restarts` or any other final semantics. Default: unset.
|
Full Usage:
this.OnStormPause
Parameters:
Action<SupervisorStormPauseEvent>
Returns: Supervisor
|
Observe failure-storm pauses live: `handler` runs synchronously, from the supervision loop, right before each pause is slept out — see `StormPause`. Same synchronous, keep-it-quick contract as `OnRestart`. No effect unless `StormPause` is set. Purely additive: does not change `SupervisionOutcome.StormPauses` or any other final semantics. Default: unset.
|
|
|
Full Usage:
this.RunAsync
Parameters:
CancellationToken
Returns: Task<Result<SupervisionOutcome, ProcessError>>
|
Supervise until the policy, the predicate, or the restart budget ends it, and report the `SupervisionOutcome`. Returns `Error` only when the *terminating* attempt failed to produce a result at all (a spawn/IO failure with no further restart allowed) — there is no final result to report. A spawn failure with restarts remaining counts as a crash and is retried. An incarnation cancelled via its token is terminal: supervision returns that `Cancelled` immediately, regardless of policy or budget.
|
Full Usage:
this.StopWhen
Parameters:
Func<ProcessResult<string>, bool>
Returns: Supervisor
|
End supervision when `predicate` matches a completed run — checked before the `RestartPolicy` on every exit, clean or not. (It never sees a run that failed to *start*; spawn errors are classified by the policy alone.)
|
|
Enable the **failure-storm guard**: when crash-restarts cluster faster than the failure score can decay, pause restarts once for `pause` (jittered per `Jitter`), then reset the score and resume. Off by default. Pauses taken are reported in `SupervisionOutcome.StormPauses`.
|
|
Run every incarnation through `runner` instead of the default `JobRunner` — e.g. a shared `ProcessGroup` runner for one kill-on-drop group, or a test double.
|
ProcessKit API Reference