- actor/<id>/ holds actor.wasm, actor.json, source/, public/, data/, versions/ - the broker boots by scanning actor/; a changed actor.wasm is an upgrade, a rejected one stays in versions/ with a reason and the files come back - /actor hides every data/ folder - subscriptions live in the subscriber's actor.json - cargo installs the SDK and points sausage-actor at it; no build limits - PROTOCOL.md
5.4 KiB
The Sausage protocol
This document is the contract between the host and the actors. An actor in any language that follows it works in Sausage.
Actors
An actor is a WASI 0.2 command (wasm32-wasip2): a program with main. The
host runs one instance per actor. An instance can run forever.
The instance gets:
- stdin: one message per line, from the broker
- stdout: one message per line, to the broker
- stderr: free text, appended to
/actor/<id>/stderr.log - environment:
SAUSAGE_ACTOR(its ID),SAUSAGE_VERSION(its version), andSAUSAGE_CANDIDATE=1during a test run - outgoing HTTP and HTTPS through
wasi:http(no raw sockets) - the filesystem described below
Messages
A line on stdin:
{"id": "m-17", "from": "console", "reply_to": "agent/a5", "body": {"type": "text", "text": "hi"}}
A line on stdout:
{"to": "console", "id": "a6", "reply_to": "m-17", "body": {"type": "text", "text": "hello"}}
bodyis required. By convention it is an object with a"type".tois the recipient. Withoutto, the broker publishes the message to every subscriber of the sender.idis optional. If the actor sets it, the message ID is"<sender>/<id>". Otherwise, the broker assignsm-<n>. The broker always setsfrom.reply_tomarks a reply. There is no request/response: a reply is a new message whosereply_tois the ID of the message it answers.- Delivery is at most once. Each actor has a mailbox of 1,000 messages. The mailbox keeps messages while the actor restarts or is upgraded.
- If the broker cannot deliver a message, the sender gets
{"type": "undeliverable", "reason": "...", "to": "..."}frombroker.
Before the broker stops an instance, it sends
{"id": "broker/shutdown", "from": "broker", "body": {"type": "shutdown"}}
and then closes stdin. After 5 seconds, it kills the instance.
Filesystem
Each instance sees three folders:
| Path | Access | Contents |
|---|---|---|
/data |
read-write | private state of the actor |
/public |
read-write | files that the actor publishes |
/actor |
read-only | the actor/ folder of the world |
/actor/<id>/ contains actor.wasm, actor.json, source/, public/,
status.json, stderr.log, and versions/<n>/ of every actor. The data/
folders (and the data snapshots in versions/) are hidden.
A new or truncated file becomes visible when the writer closes it. Before that, readers see the old file or no file. If the instance crashes, the write is lost. A write to an existing file without truncate (append, edit) goes in place.
Send large or binary data as a path under /actor/<id>/public/, not inside a
message.
The broker
Requests to broker (the reply has the same reply_to):
type |
Fields | Effect |
|---|---|---|
spawn |
actor, wasm, source?, built_by? |
test, then start a new actor |
upgrade |
actor, wasm, source?, built_by? |
test, then swap the version |
rollback |
actor, version? |
go back to an older version and its data |
stop, start |
actor |
stop or start an actor |
remove |
actor |
move the actor's folder to archive/ |
subscribe, unsubscribe |
actor |
receive (or not) what actor publishes |
scan |
boot new or changed actor.wasm files |
|
list |
all actors and their state |
Paths: /actor/..., /public/... (the sender's), or /data/... (the
sender's).
Versions. versions/<n>/ keeps every version that the broker saw:
actor.wasm, source/, and meta.json. A version that the broker refused
has "rejected" in its meta.json.
Tests. Before a spawn or an upgrade, the broker runs the candidate on a
copy of the actor's /data and /public, without HTTP. The tests are JSON
files in source/tests/*.json:
{
"description": "add, then list",
"steps": [
{"send": {"type": "add", "text": "milk"}, "expect": {"type": "added"}},
{"send": {"type": "list"}, "expect": [{"type": "notes"}]}
]
}
An expect entry matches when it is a subset of the reply body. A step can
also have from (the sender, default test) and to (the expected
recipient). Each file runs in a fresh instance. Without test files, the
candidate must stay up for 2 seconds.
Swap. The broker holds the mailbox, stops the old instance, saves a
snapshot of /data and /public in the old version's folder, installs the new
files, and starts the new instance.
Crashes. The broker restarts a crashed actor (0.5 s, then doubling, at
most 30 s). It keeps the message that the actor got just before the crash,
and delivers it again after the next upgrade. A new version that crashes 3
times within 30 seconds after an upgrade goes back to the previous version,
with its data. The actor that asked for the upgrade gets rolled_back.
The folder is the truth. At start, and on scan, the broker boots every
actor/<id>/actor.wasm. If the file differs from the installed version, the
broker copies it to versions/<n>/ and upgrades to it. If the upgrade fails,
the installed files come back.
The cargo actor
{"type": "build", "source": "/actor/agent/public/drafts/notes"}
The reply is {"type": "built", "wasm": "/actor/cargo/public/builds/<n>/<name>.wasm", ...}
or {"type": "build_failed", "errors": "..."}. A Rust actor depends on the SDK
with sausage-actor = "0.1". cargo points that dependency at the SDK of the
host.