jonnify·bcs /bcs/elixir-core/otp-application/the-blast-radius

B2.1 · dive 3 — the blast radius

These die together — or not.

R4 crashes the portfolio store while the order store holds a position row: sibling untouched (one_for_one) — ord_store pid stable, row intact through prt_store's crash. A restart strategy is a blast-radius statement, and choosing anything wider than one_for_one is a written claim that the named children die together.

Source: content/bcs2.1.md, quoting bcs_rung_2_1_check.out; the supervisor is committed at lib/echo_data/bcs/supervisor.ex.

The crash, on the tree. EchoData.Bcs.Supervisor, one_for_one over two named stores — the tree the rung booted. Select the crash to read what R4 recorded.
EchoData.Bcs.Supervisor strategy: :one_for_one prt_store the crash, R4 dies, restarts alone ord_store the sibling pid stable · row intact
readout · Select the crash to read what R4 recorded: prt_store dies and restarts alone; ord_store's pid is stable and its row is intact. The full transcript is frozen in §1 below.
§1

The transcript

bcs_rung_2_1_check.out · verbatim · this dive reads R4 and the boot line
content/echo_data/runtimes/elixir/bcs_rung_2_1_check.out · verbatimfrozen
boot: two stores under one_for_one; native codec self-check passed at each init
R1 surface ok -- exports: six domain functions plus OTP callbacks, nothing else -- no table, no pid, no internals
R2 existence ok -- existence restored, data not: fresh table after kill -- durability is a different chapter's job
R4 radius ok -- sibling untouched (one_for_one): ord_store pid stable, row intact through prt_store's crash
R3 checkpoint ok -- recovered through the boundary, not the heap: re-put from a read-back row
R5 gate ok -- prt_store refuses an ORD name with {:error, :namespace} -- admitted kinds are per-boundary
PASS 5/5
§2

Start order is the dependency declaration

content/bcs2.1.md · What/Where · supervisor.ex

Children start left to right and stop in reverse [1], so the list in EchoData.Bcs.Supervisor is documentation the runtime enforces. The supervisor, committed verbatim:

def init(stores) do
  children =
    for {name, ns} <- stores do
      Supervisor.child_spec({EchoData.Bcs.PropertyStore, [name: name, namespace: ns]},
        id: name
      )
    end

  Supervisor.init(children, strategy: :one_for_one)
end

The proper Application callback module arrives with the umbrella adoption rung, where a Mix project exists to declare it in. Restart intensity defaults (the supervisor's maximum restart frequency [1]) are untested at their limit here — the gates kill twice, far under it.

§3

Strategy is a written claim

content/bcs2.1.md · What/When/Decisions

Under one_for_one, when a child terminates, “only that child process is affected” [1] — and choosing anything wider is therefore a written claim that the named children die together. Choose one_for_one by default and depart only with a written reason: stores with shared startup assumptions earn rest_for_one, and a genuine die-together cluster earns one_for_all, each departure a sentence in the design, not a flag in the code. For operators, the supervision tree is the runbook: the strategy column answers “what cascades,” the start order answers “what depends on what,” and both are code.

The radius, computed. A pure function over a fixed three-child tree (the stores in start order: store_a, store_b, store_c). Select a strategy and a crashed child; the readout lists the restart set the strategy declares.
readout · Select a strategy and a crashed child; the restart set is computed from the start order.

Static verdicts, crash store_b: one_for_one → store_b alone · rest_for_one → store_b, store_c (the crashed child and every child started after it) · one_for_all → store_a, store_b, store_c. Crash store_a under rest_for_one → all three; crash store_c under rest_for_one → store_c alone.

§4

The Go counterpart — the supervise loop

content/bcs2.1.md · How

The application shape translates without OTP: a package whose Start(ctx) spawns the owner goroutine from Chapter 1.1 and returns a handle of typed methods — the export list, enforced by the package boundary — and whose supervisor is a restart loop, quoted verbatim:

func supervise(ctx context.Context, run func(context.Context) error) {
    backoff := 100 * time.Millisecond
    for ctx.Err() == nil {
        if err := runRecovering(ctx, run); err != nil {
            time.Sleep(backoff) // existence restored next loop;
            backoff = min(backoff*2, 2*time.Second)
            continue // data was the goroutine's locals: gone, by design
        }
        return
    }
}

Existence guarded by the loop, data by nothing — the same statement R2 gates, made in a language with no supervisor behaviour. One loop per system is the one_for_one analog; a shared loop over several owners would be one_for_all and should be exactly as deliberate.

§5

References

sources · related

Sources

Related