4.9 KiB
Cycle 18: OneForAll and RestForOne Supervisor Strategies — Development History
Commit:
771c38c· 4 files · 277 insertions, 2 deletions
Motivation
Cycle 17 introduced supervision with the OneForOne strategy (only the failed child is restarted). Erlang/OTP defines two additional coordinated restart strategies that handle interdependent children:
one_for_all— when one child fails, ALL children are restarted (for tightly coupled children that share state assumptions)rest_for_one— when one child fails, it and all children started AFTER it are restarted (for chains where later children depend on earlier ones)
These strategies require coordinated shutdown: the supervisor must stop living siblings, wait for all of them to die, then restart the affected set in the original spec order.
Research Detour: SmallBox/InlineAny Optimization
Before choosing this cycle's topic, investigated SmallBox optimization for message dispatch — a 44% queue throughput improvement was measured. However, it was deferred because:
- Requires
unsafecode in a core path - Would touch 32+ call sites across the codebase
- Violates the "src/ structure frozen" constraint
Extended the Supervisor with coordinated strategies instead — higher value, zero risk.
Competitor Analysis
| Framework | OneForAll | RestForOne | Coordinated Shutdown |
|---|---|---|---|
| Erlang/OTP | Yes | Yes | Built into supervisor behaviour |
| Akka | No (different model: Resume/Restart/Stop/Escalate) | No | N/A |
| Ractor | No | No | N/A |
| Bastion | Implicit (redundancy groups) | No | Implicit |
| Swactor | Yes | Yes | Phase-based state machine |
Erlang's Coordinated Restart
In Erlang, one_for_all and rest_for_one stop affected children in reverse start order, wait for all to terminate, then restart in start order. This guarantees initialization dependencies are respected.
Implementation
SupervisorPhase State Machine
Normal— steady state, processing handle_down events normallyStopping { awaiting: HashSet<ActorAddress>, restart_set: Vec<usize> }— coordinated shutdown in progress
SupervisorStrategy Extensions
SupervisorStrategy::OneForAll— all children restarted when one failsSupervisorStrategy::RestForOne— failed child + all children after it (in spec order) restarted
Coordinated Restart Flow
- Child dies →
handle_downcalled - Strategy determines affected indices (OneForAll: all, RestForOne: failed + later)
begin_coordinated_restart(ctx, indices):- Sends stop signals to living siblings in the restart set
- Transitions to
Stoppingphase withawaitingset - Already-dead children handled: if all targets are already dead, skip to immediate restart
- Subsequent
handle_downcalls duringStoppingphase:- Remove from
awaitingset - When
awaitingis empty → all stopped
- Remove from
finish_restart(ctx):- Restart all children in the restart set, in spec order
- Transition back to
Normalphase
Refactoring
check_intensity()factored out ofhandle_downfor restart budget checking — shared by all strategies
Key files modified: src/actor.rs, tests/runtime_api.rs, docs/runtime.md
Design Decisions
- Phase-based state machine — the
Stoppingphase cleanly separates "waiting for siblings to die" from "normal operation." This prevents races where a new death arrives while a coordinated restart is in progress. - Stop signals (not kill) — affected siblings are stopped gracefully (PoisonPill semantics), giving them a chance to run
on_stopfor cleanup. This matches Erlang'sterminate/2being called during supervised shutdown. - Restart in spec order — children are restarted in the order they appear in the ChildSpec list, regardless of which child triggered the restart. This preserves initialization dependencies.
- Already-dead optimization — if all children in the restart set are already dead (e.g., cascading failures), skip the
Stoppingphase entirely and restart immediately. Without this, the supervisor would wait forever for Down messages that already arrived. - Meltdown protection shared — the same
max_restartsbudget applies across all strategies. OneForAll restarts count as one restart event (not N), matching Erlang's behavior.
Tests Added
3 new tests (138 → 141 total):
supervisor_one_for_all_restarts_all_on_single_failure— one child panics, all 3 get new addressessupervisor_rest_for_one_restarts_rest_after_failed— child_b panics, child_a unchanged, child_b + child_c restartedsupervisor_one_for_all_waits_for_all_downs_before_restart— verifies coordinated shutdown completes before restart begins
Result
- 141 tests pass (133 behavioral + 7 proptest + 1 doctest)
- Zero warnings, full workspace compiles
- All three Erlang-standard supervision strategies now available: OneForOne, OneForAll, RestForOne