Resilience
Goal: let processes fail and restart safely (supervision, links, and monitors), all of it emerging from the structure of the code (the BEAM model, with no behaviours declared separately).
Death is an error value
Section titled “Death is an error value”When a process dies, the sequence is fixed: its defers run, its @mm is freed, and
an error describing the death is produced (a crash with a reason, or a clean exit). That error is what
reaches the observer: it is error-as-value applied to process death.
Monitor = the catch of a supervised spawn
Section titled “Monitor = the catch of a supervised spawn”You don’t need a new construct to “be told of the death without dying along with it”: it is the catch |e|
of spawn:
@supervisor(max_restarts: 3, window: 10s)spawn worker(data) catch |e| { // 'worker' died, I (the supervisor) didn't. 'e' is the reason. I react without propagating. log("worker fell: {{e}}")}Since death is an ordinary error, you discriminate it with match |e|:
@supervisor(strategy: rest_for_one)spawn db(conn) match |e| { Normal => log("finished the work") Crashed(m) => restart() Timeout => escalate()}The three strategies emerge from the structure
Section titled “The three strategies emerge from the structure”one_for_one: independent processes, each with its owncatch. With no annotation, it is the default (no restart limit):
spawn worker_a(da) catch |e| { ... }spawn worker_b(db) catch |e| { ... } // if A falls, B doesn't knowone_for_all: aspawn-block, where the processes live and die together. If one falls, the coordinator kills the siblings and restarts the group:
spawn { indexer(docs) catch |e| { log(e) } notifier(subs) catch |e| { log(e) }} catch |e| { notify_ops("critical group died: {{e}}") // the whole group exhausted itself}rest_for_one: a pipeline with order; the only one that requires annotation (it is not inferable):
@supervisor(strategy: rest_for_one)spawn { db_connection() // falls → restarts all three db_writer() // falls → restarts writer + reader db_reader() // falls → restarts only reader} catch |e| { ... }Links and monitors, without raw primitives
Section titled “Links and monitors, without raw primitives”BEAM has link/monitor/trap_exit as primitives; Makoto does not expose them raw. Coupling
fate is the tree (processes in the same spawn-block share fate because the structure says
so), and observing without coupling is the catch. Invisible death cascades go away: the supervision
tree is the code.
Retry belongs to the caller
Section titled “Retry belongs to the caller”The process function is clean, with no built-in retry policy; the @supervisor at the call site decides
max_restarts/window. When it exhausts, the outer catch fires, with no automatic escalation.
Next: 08 · Memory