hanki

supervisor

stdlib/extra/supervisor.hk: restart-policy bookkeeping for actor supervision.

The supervision wiring is per-actor worked code and no generic module: a handler cannot be passed as a value and spawn takes a static actor name, which leaves a reusable send_after/Delayer/supervisor unable to be a single export. See the timer and restart patterns in HANKI.md §15. What is reusable is the decision: after a child has died N times, should the supervisor restart it (and after how long a backoff) or give up? That pure bookkeeping sits here, free of any actor or clock dependency, which leaves it testable in isolation and shared across supervisors.

This module counts consecutive restarts. Restart-per-window policies (at most N restarts per T milliseconds) need clock reads (time.now_ms!, §15) to measure the window and are a later addition.

RestartDecision

type RestartDecision
  Restart(i32)
  GiveUp
end

What a supervisor should do after a child's death: restart it after a backoff of the given milliseconds, or stop trying.

impl Eq<RestartDecision>

Hand-written (not @derive) so it sits in the baked stdlib prefix: a stdlib body compares RestartDecision, and a stale-blob chunked lower must resolve Eq<RestartDecision> from the prefix and never an impl synthesised after the user items. Mirrors @derive(Eq); the build_stdlib_bytecode assert pins it there.

eq?

def eq?(self, other: Self) -> bool

Two decisions are equal when they are the same variant and, for Restart, carry the same delay.

Restart(50i32).eq?(Restart(50i32)) => true
Restart(50i32).eq?(Restart(80i32)) => false
(GiveUp == GiveUp)                 => true

RestartPolicy

struct RestartPolicy
  max_restarts: i32
  base_ms: i32
  max_ms: i32
end

A consecutive-restart policy with exponential backoff. max_restarts is how many restarts to allow before giving up; the backoff delay starts at base_ms and doubles each restart, capped at max_ms.

impl RestartPolicy

next

def next(self, restarts: i32) -> RestartDecision

Decide what to do for a child that has already been restarted restarts times. Restart(delay_ms) once more is allowed, carrying the backoff to wait first; GiveUp once restarts reaches max_restarts.

p = RestartPolicy(max_restarts=3i32, base_ms=10i32, max_ms=1000i32)
p.next(0i32)  => Restart(10i32)
p.next(1i32)  => Restart(20i32)
p.next(2i32)  => Restart(40i32)
p.next(3i32)  => GiveUp

backoff

def backoff(self, restarts: i32) -> i32

The backoff delay before the restarts-th restart: base_ms doubled restarts times, saturating at max_ms. Pure; exposed on its own so a supervisor can log or adjust the schedule.

p = RestartPolicy(max_restarts=5i32, base_ms=10i32, max_ms=50i32)
p.backoff(0i32)  => 10i32
p.backoff(2i32)  => 40i32
p.backoff(3i32)  => 50i32