swactor/docs/transport.md
Developer 548396ba4a feat: in-browser runtime, WebSocket gateway, dashboard improvements
- NodeAddr abstraction replacing SocketAddr across distribution crate (~30 files)
- Core wasm32 compatibility: web-time, cfg-gated threads, wire encoding extraction
- WebSocket transport (browser) with GatewayControl protocol
- WebSocket gateway (native) with session routing and control protocol
- BrowserRuntime API: JS actor support, connect/spawn/send/tick
- Dashboard: extracted HTML to static files, gossip protocol panel with
  membership event log, visual fixes, click-to-filter, worker-colored rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:51:36 +00:00

6.4 KiB

Transport-Agnostic Messaging

Enables actors on different runtimes to communicate transparently via pluggable codecs (serialization) and transports (delivery protocol).

Feature-gated: #[cfg(feature = "transport")]. Without the flag, the binary is identical to the baseline runtime.

cargo build --features transport
cargo test  --features transport

Two Layers of Pluggability

  • Codec<M> — HOW bytes are encoded. gRPC/protobuf, bincode, custom, etc. No serde bounds — the codec defines what it needs from M.
  • Transport — WHERE bytes are sent. InMemory (testing), TCP, gRPC, etc.

See transport_routing.svg for the extended routing chain, and transport_encode_decode.svg for the encode/decode data flow.

Core Types

Type Role
Codec<M> User-implemented encode/decode for a message type
NetworkMessage Marker trait: adds fn type_tag() -> &'static str for wire routing
WireEnvelope { dest: ActorAddress, type_tag: String, payload: Vec<u8> }
Transport fn send(WireEnvelope) -> Result<()> — pluggable delivery
CodecRegistry Maps TypeId → encoder (send side) and type_tag → decoder (receive side)
TransportRouter Maps ActorAddress → Arc<dyn Transport> for remote addresses
TransportBridge Deserializes incoming WireEnvelope → (ActorAddress, Box<dyn Any + Send>)
InMemoryTransport mpsc-backed transport for testing

Routing Chain

Without transport, unresolved addresses fall through to InboxRegistry. With transport enabled, a third step is inserted:

  1. AddressMap::lookup → local worker delivery (zero-copy, no serialize)
  2. InboxRegistry::contains → external inbox delivery
  3. TransportRouter::lookup → codec.encode + transport.send (remote)
  4. Fallback → InboxRegistry::try_deliver (Err if not found)

This chain runs in Runtime::send_to, ContextInner for Runtime, and WorkerContext::send_any — all three follow the same logic.

Type Erasure Bridge

Messages are Box<dyn Any + Send> before routing, but dyn Any can't be serialized. The bridge:

Send: Box<dyn Any> → (*msg).type_id() → encoders[TypeId] → downcast to M → Codec<M>::encode → (type_tag, Vec<u8>) → WireEnvelope

Receive: WireEnvelope → decoders[type_tag] → Codec<M>::decode → Box::new(msg) as Box<dyn Any + Send> → runtime.deliver_raw(addr, msg)

TypeId (compiler-assigned, process-local) is used for encoding. type_tag (user-defined, stable) is used on the wire for decoding.

Setup

// 1. Register codecs
let mut codecs = CodecRegistry::new();
codecs.register::<Ping, _>(MyCodec);
codecs.register::<Pong, _>(MyCodec);

// 2. Create transport + router
let (transport, rx) = InMemoryTransport::pair();
let router = TransportRouter::new();
router.add_route(remote_addr, transport);

// 3. Attach to runtime
let mut rt = Runtime::new(RuntimeConfig::default());
rt.set_codec_registry(Arc::new(codecs));
rt.set_transport_router(Arc::new(router));

// 4. Send transparently
rt.send_to(remote_addr, Ping { value: 42, reply_to: inbox_addr }).unwrap();

// 5. Receive side: bridge deserializes, deliver_raw injects
let bridge = TransportBridge::new(codecs_arc);
let (addr, msg) = bridge.receive(wire_envelope).unwrap();
rt.deliver_raw(addr, msg).unwrap();

Address Resolution

Addresses are not automatically discovered. Each runtime must be told which remote addresses exist via router.add_route(). Since addresses are 32 random bytes, runtimes must exchange them out-of-band (e.g., over the TCP connection itself — see examples/tcp_ping_pong.rs).

Wire Format

The core transport module provides canonical encode/decode functions for the binary wire format. These are used by both the TCP transport (distribution crate) and the WebSocket transport (wasm crate):

  encode_wire_envelope(envelope) → bytes:
  ┌──────────────┬───────────────┬──────────────┬──────────┬─────────┐
  │ frame_len    │ dest address  │ tag_len      │ type_tag │ payload │
  │ 4 bytes BE   │ 32 bytes      │ 4 bytes BE   │ N bytes  │ rest    │
  └──────────────┴───────────────┴──────────────┴──────────┴─────────┘

decode_wire_envelope(frame) takes the bytes after the 4-byte length prefix and returns a WireEnvelope. The caller is responsible for reading the length prefix and providing exactly frame_len bytes.

WebSocket Transport (Browser)

The swactor-wasm crate provides WsTransport — a Transport impl over web_sys::WebSocket. It uses the same wire format as TCP (binary frames with length prefix). Messages are buffered until the WebSocket connection opens.

A GatewayControl protocol (JSON over reserved null address) handles actor registration and keepalive between browser and gateway.

See crates/wasm/src/ws_transport.rs and crates/gateway/ for the browser and native sides respectively.

Limitations

  • No automatic discovery — manual address exchange required
  • Transport::send is synchronous — blocking transports stall the worker
  • One route per address — no wildcard/prefix routing
  • No ordering guarantees across transports (depends on transport impl)
  • No back-pressure from remote — fire-and-forget delivery
  • TypeId is not stable across compilations (only used process-locally; wire uses type_tag)

Where Things Live

Concept File
All transport types src/transport.rs
InboxRegistry::contains() src/delivery.rs
Transport fields on TickContext src/delivery.rs
Runtime::deliver_raw, setters src/runtime.rs
Transport fallback in worker src/worker.rs
Wire encode/decode src/transport.rs
Integration tests tests/transport_api.rs
TCP example examples/tcp_ping_pong.rs
WebSocket transport (browser) crates/wasm/src/ws_transport.rs
Gateway control protocol crates/wasm/src/protocol.rs
WebSocket gateway (native) crates/gateway/src/lib.rs
WS acceptor crates/gateway/src/ws_acceptor.rs
Gateway example examples/ws_gateway.rs