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. Scheduling actor.send_after!(target.handler(args), ms) is a send site too. 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.
Delayed cast, also a compiler special form and no declarable fn:
actor.send_after!(target.handler(args), ms) # ms: i32 -> ()
It copies or moves the handler arguments now, reserves one target-mailbox slot now, and delivers the cast after ms; a negative delay clamps to zero. Scheduling has [time, throws actor.SendFailed]. A full or dead target throws synchronously, while an accepted timer cannot later fail for MailboxFull. Target death or program exit cancels the retained envelope and releases its reservation. Deterministic runs use virtual time, and timers at one deadline fire in registration order.
Arriving here for something this module does not hold
- Stopping an actor.
actor.shutdown!(target, kill_after=ms)is a compiler special form and nodef; the deadline is optional andi32.Shutdownbelow is what it answers, andExplicitShutdownabove is the death cause it raises. It also reaches a child process or a long database call the target is blocked in, andextra/processandextra/sqliteboth point back at it. - Restart policy.
extra/supervisorhas it. This module gives a supervisor the facts it decides on -ActorId,DeathCause, and theon actor_diedhandler they arrive through, and stops there. Useactor.send_after!to schedule the chosen restart without parking the supervisor's handler. - Spawning and sending. Also special forms (
spawn,c.op(),await), and they are in HANKI.md §15 and not in this file. What is here is the vocabulary those forms fail with.
impl<T> ActorRef<T>
Identity-bearing view of an actor handle. The runtime supplies the declared actor name beside the packed slot-plus-generation id; equality on the resulting ActorId compares the packed id alone.
id
def id(self) -> ActorId
The actor's stable identity. This is the same ActorId delivered as who when the actor later dies, and two handles for actors of the same declared type still return different ids. @no-doctest: an ActorRef exists only after a runtime spawn
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
class: 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.
class is the dying actor's declared name, the runtime's own actor_class for that slot, and name below reads it.
impl ActorId
name
prop name(self) -> string
The actor's declared name, Worker for a spawn Worker. A supervisor reads it to tell one dead child from another when its children are of different types.
It does not identify a child on its own: two spawn Workers under one supervisor share this name. Compare the ActorId itself to tell those instances apart. @no-doctest: the runtime constructs every ActorId; user code cannot make one to read
impl Eq<ActorId>
eq?
def eq?(self, other: Self) -> bool
Actor identity is the packed slot-plus-generation word. class is a diagnostic label and may be unavailable after slot reuse. It never participates in equality. @no-doctest: the runtime constructs every ActorId; user code cannot make one to compare
impl Hash<ActorId>
hash
prop hash(self) -> u64
Hashes the identity word Eq compares, which lets an ActorId key a Map. @no-doctest: the runtime constructs every ActorId; user code cannot make one to hash
Shutdown
type Shutdown
Terminated
AlreadyDead
SelfScheduled
TimedOut
end
Outcome of actor.shutdown!(target, kill_after=ms), read by awaiting the returned Future<Shutdown>. The deadline is optional. Every outcome is normal, and the future therefore has no throw and is freely discardable for fire-and-forget. The runtime constructs these values.
stop_all!
def stop_all!<T>(targets: List<ActorRef<T>>, kill_after: i32) -> List<Shutdown>
Stop a set of actors and report what happened to each, in the order given.
The completion boundary a request-scoped operation returns through. Each target is signalled first and every outcome is collected afterwards, which puts one deadline over the whole set. Stopping them one at a time takes kill_after per target.
TimedOut names a target that reached no shutdown safe point in time. Its shutdown request remains active and it may terminate later, and the caller has a report and no guarantee. HANKI.md section 15 has the surrounding pattern and the two limits behind that report: a target wedged in native compute on the AOT tier reaches no safe point, and a stopped actor's own children wind down asynchronously. @no-doctest: an ActorRef exists only after a runtime spawn