smarm-agent a866e34b52 mailbox: typed-path producers, by-name gen_servers, root-exit teardown (RFC 014)
Implements the new addressing surface the examples in examples/ specify:

- spawn_addr::<A>(FnOnce(Receiver<A::Msg>)) -> Pid<A>: the typed-path
  producer. Makes the inbox, spawns the body with its receiver, and publishes
  the sender from the PARENT side (registry::install_for) before returning the
  pid, so an immediate send_to always resolves (no race on the body installing
  itself). Detached, like ServerBuilder::start.

- lookup_as / pick_as / members_as: re-type an erased pid as Pid<A> over the
  heterogeneous registry/pg stores, sharing one helper (pid::assert_type).
  Unchecked but sound: routing is by message TypeId, so a wrong A degrades to
  SendError::NoChannel on the next send, never a misdelivery.

- dispatch::<A>(group, msg) -> Result<Pid<A>, SendError<A::Msg>>: pick_as +
  send_to, with the message handed back on failure. New SendError::NoMember
  variant for the empty/all-dead group case.

- gen_server naming: ServerName<G> backed internally by the registry's existing
  typed-channel store keyed by TypeId::of::<Envelope<G>>() — Envelope stays
  private, no separate directory. Type-state NamedServerBuilder<G> keeps the
  current infallible ServerBuilder::start() untouched; its start() is fallible
  (parent-side register, NameTaken). Free call/cast/whereis_server resolve per
  use. ServerRef::shutdown (+ free shutdown) is the sys-style synchronous stop.

- root-exit teardown: the run's initial actor is recorded as root; when it
  finalizes it flags root_exited, and the scheduler's idle verdict then stops
  the parked-forever remainder (a one-shot RootDrain sweep). Deferred to the
  idle point on purpose: the run queue drains first, so actors with queued work
  finish rather than unwinding on the stop. This lets a named-server daemon (or
  any pinned actor) wind the run down instead of hanging on live_actors > 0.

request_stop is refactored to a request_stop_inner core so the sweep can drive
it from inside the runtime without re-borrowing the thread-local.

Lib + tests + examples build warning-free; full suite green.
2026-06-18 11:02:27 +00:00
2026-05-26 23:14:46 +02:00

smarm

SMARM — Smarm, Marks Actor Runtime Machinery. A proof-of-concept green-thread actor runtime for Rust.

Implements the core ideas in Achitecture.md: green-thread actors on a shared heap, scheduled cooperatively, communicating only by Send messages. Erlang's isolation model without Erlang's copying GC, Rust's zero-copy ownership transfers without async's function colouring.

The scheduler is multi-threaded — one OS thread per available CPU, all drawing from a shared run queue. The single-threaded run() entry point is kept as a convenience wrapper around runtime::init(Config::exact(1)).run(f).

What's here

Module What it does
stack mmap'd growable stack with guard page; SIGSEGV on overflow
context #[naked] x86-64 context-switch shims, callee-saved regs only
preempt Allocator-driven preemption; check!() macro for no-alloc loops
pid (index, generation) PIDs; stale handles are detectable, not silent
actor Trampoline + catch_unwind boundary at the actor entry point
scheduler Run queue, slot table, spawn/join, parking, idle path
channel Unbounded MPSC channel; recv parks the actor; recv_timeout bounds it; select/select_timeout park on many receivers at once (ready-index, priority order)
mutex Mutex<T> with mandatory timeout; FIFO waiters; parks the green thread
timer Min-heap of (deadline, reason); Sleep and WaitTimeout reasons
io block_on_io for blocking work; wait_readable/wait_writable + read/write via epoll
supervisor Signal::Exit/Panic/Stopped funnelled to a parent; OneForOne/OneForAll/RestForOne strategies + restart-intensity cap
monitor monitor(pid)Monitor { id, target, rx }; one-shot Down via rx; demonitor(&m) tears one registration down; unidirectional death notice
link bidirectional link/unlink; abnormal death propagates (cooperative stop, or an ExitSignal message under trap_exit)
gen_server call/call_timeout (sync request-reply) / cast (async) over one inbox; handle_info over static info arms + handle_down via Watcher-fed monitors, selected ahead of the inbox; ServerRef/ServerBuilder + init/terminate hooks; server-down via channel closure
registry register/whereis/name_of: name ↔ pid bimap; lazy generation-checked cleanup

Quick taste

use smarm::{run, spawn, channel};

run(|| {
    let (tx, rx) = channel::<i64>();
    let h = spawn(move || {
        for _ in 0..3 {
            let v = rx.recv().unwrap();
            println!("got {v}");
        }
    });
    for v in 1..=3i64 {
        tx.send(v).unwrap();
    }
    h.join().unwrap();
});

Layout

src/
  stack.rs context.rs preempt.rs pid.rs actor.rs
  scheduler.rs channel.rs mutex.rs timer.rs io.rs
  supervisor.rs monitor.rs link.rs runtime.rs
  gen_server.rs lib.rs
tests/
  per-module integration tests
benches/
  primes.rs    fan-out/fan-in compute, vs tokio current_thread

Building and running

Standard Cargo. Requires Rust 1.95 or newer (the #[naked] attribute went stable in 1.88; we use a few unrelated post-1.88 features). master is x86-64 Linux only. An experimental, untested aarch64 context-switch backend lives on the arm-port branch (extracted into a target_arch-gated src/arch/); it has not been validated on hardware yet. macOS remains on the deferred list because of the epoll dependency.

cargo test                # all tests
cargo test --test mutex   # one module
cargo bench               # primes benchmark vs tokio

What's not here

See the Defer section of Architecture.md. join! for handle groups, stack growth via remap, hierarchical timer wheel, fd-wait timeouts, Signal::Timeout. Each is mechanism we know how to add; none belongs in this iteration.

Docs

Document What it covers
Architecture.md Design intent, runtime model, and deferred work
smarm - Deep Dive.html Generated walkthrough of the system; good starting point
BENCHMARKS_AND_TUNING.md Where smarm wins and loses vs tokio, preemption knob recommendations
benchmarks.md Raw benchmark results, methodology, and tuning experiment log

Contributing

This is a personal proof-of-concept. There's no PR workflow. If you fork it and do something interesting, just send me an email. If it's nice, I'll upstream the changes.


S
Description
SMARM - Smarm, Marks Actor Runtime Machinery
Readme MIT
998 KiB
Languages
Rust 98%
Python 1.7%
Shell 0.3%