Lay down the wasm-actor host, a frontend-agnostic command layer, and a distribution registry. - command (new crate): CommandRouter dispatching to built-in inspection handlers (overview/workers/actors) plus user-registered handlers, with line and query-param parsers; built for REPL/REST/TUI/WebSocket frontends. - wasm-actor (new crate): skeleton host — WasmActor, Builder, Engine, error types — with echo/double/silent guest fixtures and integration tests. - distribution: add Registry (member catalog + lookups) and Snapshot, with tests. - core: extend the worker watch API; add watch_api integration tests. Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
5.5 KiB
5.5 KiB
Wasm Actor
The swactor-wasm-actor crate runs WebAssembly guest code inside a swactor
actor. The Wasm instance is sandboxed by wasmtime.
Architecture
┌─ Runtime ──────────────────────────────────────────────────────────────┐
│ │
│ ┌─ WasmActor ──────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Store<HostState> -- wasmtime store with outbox │ │
│ │ Memory -- guest linear memory │ │
│ │ alloc: TypedFunc -- guest allocator │ │
│ │ handle: TypedFunc -- guest message handler │ │
│ │ │ │
│ │ impl ActorInterface for WasmActor │ │
│ │ Incoming = ByteMessage │ │
│ │ Response = () │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Native Actors ─────────────────────────────────────────────────┐ │
│ │ (can exchange ByteMessage with WasmActors normally) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
Message Flow
Host Guest (Wasm)
──── ────────────
ByteMessage arrives
│
├─1─ call alloc(len) ──────────► bump-allocate, return ptr
│
├─2─ write bytes at ptr ───────► (memory updated)
│
├─3─ call handle(ptr, len) ────► process message
│ │
│ ◄── swactor.send() ────────────┤ (0..N times)
│ (buffered in HostState.outbox) │
│ │
├─4─ drain outbox ◄────────────── handle returns
│
v
ctx.send(dest, ByteMessage) for each outbox entry
Guest Contract
Guests are standalone wasm32-unknown-unknown modules. They export three
symbols and may import one:
| Direction | Module | Symbol | Signature |
|---|---|---|---|
| export | — | memory |
linear memory |
| export | — | alloc |
(i32) -> i32 |
| export | — | handle |
(i32, i32) -> () |
| import | swactor |
send |
(i32, i32, i32) -> () |
The send import takes (dest_ptr, payload_ptr, payload_len) where
dest_ptr points to a 32-byte ActorAddress in guest memory.
Usage
use swactor::runtime::{Runtime, RuntimeConfig};
use swactor_wasm_actor::{ByteMessage, SharedEngine, WasmActorBuilder};
// Create a shared engine (once)
let engine = SharedEngine::new().unwrap();
// Build an actor from .wasm bytes
let wasm_bytes = std::fs::read("my_guest.wasm").unwrap();
let actor = WasmActorBuilder::new(engine, wasm_bytes)
.build()
.unwrap();
// Use it like any other actor
let rt = Runtime::new(RuntimeConfig::default());
let addr = rt.spawn(actor).unwrap();
rt.send_to(addr, ByteMessage(b"hello".to_vec())).unwrap();
rt.tick();
Sandboxing
The SharedEngine disables all optional Wasm proposals:
- Threads — disabled
- SIMD / relaxed SIMD — disabled
- Reference types — disabled
- Multi-value — disabled
- Bulk memory — enabled (required by most Rust/LLVM toolchains)
No WASI imports are linked. Guests have no access to the filesystem, network,
clock, or random number generator. The only host function available is
swactor.send.
Where Things Live
| File | Purpose |
|---|---|
crates/wasm-actor/src/lib.rs |
ByteMessage + re-exports |
crates/wasm-actor/src/engine.rs |
SharedEngine — sandboxed wasmtime config |
crates/wasm-actor/src/builder.rs |
WasmActorBuilder — compile, link, instantiate |
crates/wasm-actor/src/actor.rs |
WasmActor — ActorInterface impl |
crates/wasm-actor/src/error.rs |
WasmActorError |
crates/wasm-actor/tests/guests/ |
Three test guest crates (echo, double, silent) |
crates/wasm-actor/tests/wasm_actor.rs |
7 integration tests |