How Programs Run

You can write a lot of ƿit knowing only that actors send each other messages. This page covers the rest of what the machinery does on your behalf, so the behaviour you see has an explanation.

Starting

Running a program creates an actor. Before your program body executes, the runtime runs every module in the executable’s closure — each one once, in dependency order, so that a module’s imports have already produced their values by the time it runs.

Each module’s returned value is stone, and within one executable it is shared: if three modules all use('config'), all three receive the same value. A second actor running the same executable repeats the whole process and gets its own values.

Then your program body runs. Typically it registers a receiver and waits.

Turns

An actor handles one message at a time. Processing one message is a turn, and a turn runs to completion: no other message is interleaved into it, and no other actor sees your half-finished state. You never need a lock to protect an actor’s own data.

Turns are why callbacks matter. Work that finishes later — a network read, a delay, a message to another actor — cannot block the turn waiting for an answer, so it hands you the answer in a later turn through a callback. See Functions.

A turn can be suspended and resumed. If a turn runs long, the scheduler may pause it and let other actors run, then continue it exactly where it stopped. Suspension is invisible to your code: locals, position, and partial work are all preserved.

Sending messages

send(actor, message) delivers a message to an actor. It arrives, and the actor handles it in one of its turns.

Where that actor is does not change how you write the code. The same send reaches an actor in the same process, in another process, or on another machine — the runtime encodes the message, moves it, and decodes it on the other side. What you send is copied, so both sides hold their own values and neither can reach into the other’s memory.

To reply, send back to the message itself:

$receiver(function(message) {
  send(message, {status: "ok"})
})

The message carries the routing needed to find the sender.

Slow actors

An actor that will not yield is a problem for everyone sharing the machine, so the runtime watches for it. A turn that overruns its time is recorded as a slow strike, and a slow actor goes to the back of the line — the system stays responsive by serving everyone else first. After enough strikes the runtime may remove the actor for being abusive.

Ordinary code, even code doing substantial work, is suspended and resumed rather than struck: suspension is the normal response to a long turn. Strikes accumulate for actors that will not yield at all.

Both the threshold and what happens at it are settable — per actor, and differently for guest actors than for your own. A shop shipped with an application can be configured to be lenient, strict, or to never remove an actor at all; see the shop you ship.

Stopping

$stop() stops the current actor; $stop(child) stops one it started. Stopping releases the actor’s heap, its pending messages, and everything it held.

Actors can be coupled so that one dying takes the other with it. A child is automatically coupled to the actor that started it, so a supervising actor going away does not leave orphans behind. $couple(other) sets this up explicitly.