hanki

actor

stdlib/actor.hk: actor-runtime types surfaced to user code.

Every send picks up [throws actor.SendFailed] (HANKI.md §15). The runtime materialises a SendFailed at the send site: MailboxFull when a bounded mailbox is full, Died(ActorId, DeathCause) when the target has terminated, Timeout when actor.await_timeout! gave up waiting. ActorId and DeathCause are also delivered to a supervisor's on actor_died handler.

Bounded await, a compiler special form like the send forms and no declarable fn, since Future<T> cannot cross a fn boundary):

actor.await_timeout!(f, ms) # f: Future<T>, ms: i32 -> T

It types as await f does and raises the future's effects at the call site; if no reply lands within ms milliseconds it throws SendFailed.Timeout instead. The late reply, if one ever arrives, is abandoned: the handler still ran, and only the wait was bounded. Under --deterministic the deadline is virtual, on the gate's clock, and a timeout costs no real time and replays per seed.

Arriving here for something this module does not hold

SendFailed

type SendFailed
  MailboxFull
  Died(ActorId, DeathCause)
  Timeout
end

Send-failure throw raised at the call site of any blocking send, statement-position fire-and-forget, or matching await.

try c.op() catch e: actor.SendFailed match e MailboxFull -> retry() Died(, ) -> crash!("peer gone") Timeout -> retry() end end

DeathCause

type DeathCause
  UncaughtThrow(string, string)
  InvariantViolation(string, Option<string>, string)
  HostPanic(string)
  ExplicitShutdown
  Gone
end

Cause of an actor's termination. Delivered to the supervisor as the second argument of on actor_died(who: ActorId, cause: DeathCause) and carried as the Died payload at send-site throws. Every variant below is one the runtime produces.

impl Display<DeathCause>

to_string

def to_string(self) -> string

The same one-line wording the runtime writes to stderr when a death escalates to root, and a supervisor that renders the cause itself and an unsupervised crash describe the same event identically.

Gone.to_string() => "gone (actor id recycled)"

impl Display<SendFailed>

to_string

def to_string(self) -> string

Names the failure a catch e: actor.SendFailed arm caught. Died reports the target's name and not its raw id: the id is a packed slot-and-generation word, noise beside a name the program chose.

Timeout.to_string() => "reply timed out"

ActorId

opaque ActorId
  raw: u64
  name: string
end

Opaque identifier for a (possibly-dead) actor. The runtime constructs ActorId values on the supervisor-delivery and send-site throw paths; user code never fabricates one.

Shutdown

type Shutdown
  Terminated
  AlreadyDead
  SelfScheduled
end

Outcome of actor.shutdown!(target), read by awaiting the returned Future<Shutdown>. Every outcome is normal, the target ending up down in each case, and the future therefore has no throw and is freely discardable for fire-and-forget. The runtime constructs these values.