What is supervision?
Every actor has a parent. When a child fails (its handler panics, or records an error withctx.Err), the child’s
own supervisor, the one passed to its spawn with WithSupervisor, decides what to do, and the parent carries the
decision out. This keeps failures localized and recoverable.
A panic is matched to the rules by the type of the error it carries, exactly as an error recorded with ctx.Err is:
panic(&MyError{}) and ctx.Err(&MyError{}) follow the same rule. A panic whose value matches no rule falls under the
rule for PanicError, which every supervisor starts with (Stop), and panic(nil) follows the rule for
runtime.PanicNilError (Restart by default).
Strategies
- OneForOne: Only the failing child is affected.
- OneForAll: All siblings are affected (e.g., all restarted).
Directives
- Resume: Ignore the failure; continue processing. The child decides this on its own, without involving the parent.
- Restart: Stop the child (
PostStopruns) and start it again (PreStartruns again) on the same actor value. The mailbox and the queued messages are kept, and so are the child’s watchers and the actors it watches: a restart is not a death, so nobody receivesTerminated. The restart count accumulates and the uptime starts again. - Stop: Terminate the child.
- Escalate: Deliver a
PanicSignalto the parent’sReceive, with the child as sender, and leave the child suspended until the parent’s code acts on it (stop, reinstate or restartctx.Sender()). A top-level actor escalates to the user guardian, which ignores the signal, so it stays suspended.
Supervisor configuration
Supervisors are created withsupervisor.NewSupervisor(opts...) and passed via WithSupervisor when spawning. Key
options:
- WithDirective: Map a specific error type to a directive.
- WithAnyErrorDirective: Catch-all for any error.
- WithRetry: Bound consecutive restarts within a time window.
- WithExponentialBackoff: Delay each consecutive restart, doubling per fault.
- WithStrategy:
OneForOne(default) orOneForAll.
Resume, Restart, Stop, Escalate.
Restart budget and exponential backoff
Without any bound, a child that crashes right after every successful restart is restarted immediately, forever. This hot loop hammers whatever dependency made it fail in the first place (a database that is down, a broker that refuses connections). Two options work together to prevent it; both track consecutive faults per child, and the counter resets once the child stays fault-free for the configured window.WithRetry(maxRetries, timeout) is the restart budget: more than maxRetries consecutive faults within the window
suspends the child instead of restarting it. A suspended actor no longer processes messages, including those already in
its mailbox, but can be revived with
Reinstate. timeout is the reset window, measured from the previous fault: a new fault arriving later than timeout
after the last one starts the count over. It also bounds the pause between attempts when a restart itself keeps failing
(for example, when PreStart keeps returning an error): the attempts are spaced by about timeout, with jitter. A
non-positive timeout disables the budget. When every attempt fails, the child is stopped and its parent receives
Terminated for it.
WithExponentialBackoff(initialDelay, maxDelay, resetAfter) delays each consecutive restart: the nth restart waits
min(initialDelay << (n-1), maxDelay) before the child comes back up, giving the failing dependency time to recover.
When resetAfter is zero it defaults to maxDelay. When both options are configured, backoff’s resetAfter and delays
take precedence over timeout.
Restart budget and exponential backoff
While a restart delay is pending, the child does not process messages. As with any restart, the mailbox is kept:
messages queued before the failure are handled by the new incarnation, after
PostStart. With OneForAllStrategy, the
delay and the budget apply to the whole group: siblings restart with the same delay, and budget exhaustion suspends
them together with the faulty child.