Phase 5 — connectivity: TCP
Goal: let processes serve real traffic — listen/connect, with one process per connection so a slow or crashing client can't touch the others. Graduates: the connection-storm scenario to live data. (TLS folds into the Phase 9 secure cluster transport.)
Why this matters
The actor model's payoff for networking is that a connection is just a process. Accept a socket → spawn a process to own it. Isolation, supervision, and cheap spawning all apply to connections for free, exactly as the BEAM does it.
What we built (TDD throughout)
listen(addr, handler) -> (SocketAddr, ProcessHandle)— binds aTcpListenerand runs an acceptor process; every accepted socket is spawned as its own process runninghandler(ctx, stream). Returns the bound address (handy with port 0) and a handle to the acceptor — kill it to stop listening (dropping the listener closes the port).connect(addr) -> io::Result<TcpStream>— opens an outbound connection.- Connection-storm engine (
rusm-bench) — ramp-and-hold load reporting real connections/sec and peak concurrency.
How a developer uses it
let (addr, acceptor) = runtime.listen("127.0.0.1:0", |ctx, mut stream| async move {
// This closure runs as its own isolated process, one per connection.
let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await.unwrap();
stream.write_all(&buf[..n]).await.unwrap(); // echo
}).await?;
// ... later:
acceptor.kill(); // stop listeningDesign notes — honest about the ceiling
- The OS is the limit, not RUSM. RUSM mints processes far faster than any TCP stack hands out sockets, so sustained connection rate is bounded by the kernel's handshake/ephemeral-port/
TIME_WAITbudget — the same ceiling any runtime hits. We measure it honestly (~6–16k/s on loopback) rather than inflate it. SO_LINGER(0)to avoidTIME_WAITexhaustion. An early version showed a 291/s sawtooth: client active-close piled upTIME_WAITsockets. Closing with a reset (viasocket2::SockRef) frees them immediately, giving a sustained rate instead of a collapsing one.- Low-concurrency ramp-and-hold. Flooding with parallel connectors just exploded latency (the OS serializes handshakes anyway); a steady ramp measures the true ceiling. The fd limit is raised at startup via the
rlimitcrate.
Concepts introduced
- Process-per-connection — a connection is just another isolated process; see the process model.
Play with it
cargo run -p rusm-bench -- run connection-storm 5 # real sustained connectionsVerification
cargo test -p rusm-otp green (listen/echo round-trip, port-0 bind, kill stops listening, connect failure); connection-storm live in the dashboard. This completes the Wasm-free OTP core — rusm-otp has zero wasmtime dependency.
Next
Phase 6: Wasmtime as the process backend — each process becomes a sandboxed Wasm instance.