Update the documentation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
target
|
||||
Cargo.lock
|
||||
smarm_trace.json
|
||||
|
||||
26
README.md
26
README.md
@@ -1,8 +1,8 @@
|
||||
# smarm
|
||||
|
||||
> Silly Marks Abstract Rust Machine. A prototype green-thread actor runtime for Rust.
|
||||
> SMARM — Smarm, Marks Actor Runtime Machinery. A proof-of-concept green-thread actor runtime for Rust.
|
||||
|
||||
Implements the core ideas in [`LOOM.md`](./LOOM.md): green-thread actors on a
|
||||
Implements the core ideas in [`Achitecture.md`](.docs/Architecture.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.
|
||||
@@ -58,7 +58,6 @@ tests/
|
||||
per-module integration tests
|
||||
benches/
|
||||
primes.rs fan-out/fan-in compute, vs tokio current_thread
|
||||
LOOM.md design intent
|
||||
```
|
||||
|
||||
## Building and running
|
||||
@@ -76,7 +75,26 @@ cargo bench # primes benchmark vs tokio
|
||||
|
||||
## What's not here
|
||||
|
||||
See the **Defer** section of `LOOM.md`. Notable absences: supervisor
|
||||
See the **Defer** section of `Architecture.md`.
|
||||
restart-intensity caps, `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`](./docs/Architecture.md) | Design intent, runtime model, and deferred work |
|
||||
| [`smarm - Deep Dive.html`](./docs/smarm%20-%20Deep%20Dive.html) | Generated walkthrough of the system; good starting point |
|
||||
| [`BENCHMARKS_AND_TUNING.md`](./docs/BENCHMARKS_AND_TUNING.md) | Where smarm wins and loses vs tokio, preemption knob recommendations |
|
||||
| [`benchmarks.md`](./docs/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. I'd genuinely like to
|
||||
hear about it.
|
||||
|
||||
---
|
||||
|
||||
<sub>The name is a recursive acronym. The M is for Marks, as in the BEAM — Bogdan/Björn's Erlang Abstract Machine, the virtual machine that runs Erlang and Elixir. smarm is not the BEAM. It just admires it from a safe distance.</sub>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Loom
|
||||
# SMARM Architecture
|
||||
|
||||
> Erlang-style actor concurrency for Rust, without the copies, the colors, or the GC pauses.
|
||||
|
||||
@@ -11,7 +11,7 @@ draws the boundary, the borrow checker already enforces it. What it lacks is an
|
||||
async/await is IO-centric, colors your functions, and trades stack simplicity for state-machine complexity;
|
||||
OS threads are too heavy to spawn per actor.
|
||||
|
||||
Loom adds a third option: **green-thread actors on a shared heap**, scheduled cooperatively, with
|
||||
SMARM adds a third option: **green-thread actors on a shared heap**, scheduled cooperatively, with
|
||||
message-passing as the only cross-actor communication primitive. You get Erlang's isolation model without
|
||||
Erlang's copying GC, and you get Rust's zero-copy ownership transfers without async's cognitive overhead.
|
||||
No function coloring. No `Box<dyn Future>`. Just actors, messages, and the borrow checker doing what it
|
||||
@@ -24,14 +24,14 @@ already does.
|
||||
### Actors and scheduling
|
||||
|
||||
Each actor is a lightweight green thread with its own heap-allocated, growable stack. Stacks are
|
||||
allocated via `mmap` with a guard page below the region; overflow is detected by the OS without Loom
|
||||
allocated via `mmap` with a guard page below the region; overflow is detected by the OS without SMARM
|
||||
polling for it. Initial stacks are small and grow by remapping on demand.
|
||||
|
||||
The scheduler runs one OS thread per CPU. Each scheduler thread loops against a single global
|
||||
`Mutex<HashMap>` queue shared across all schedulers. If queue contention becomes a measured bottleneck
|
||||
this can be revisited; the interface will not change.
|
||||
|
||||
Loom requires `panic = unwind`. Users who set `panic = abort` accept that supervision and actor
|
||||
SMARM requires `panic = unwind`. Users who set `panic = abort` accept that supervision and actor
|
||||
isolation are silently degraded to process death.
|
||||
|
||||
### Process descriptor
|
||||
@@ -84,11 +84,11 @@ threshold is exceeded the actor yields. The workloads that starve a scheduler
|
||||
data transformation — are precisely the ones doing frequent allocations, so this approximation is
|
||||
correct by construction.
|
||||
|
||||
`RDTSC` is not monotonic across core migration; a slightly wrong timeslice is acceptable. Loom is
|
||||
`RDTSC` is not monotonic across core migration; a slightly wrong timeslice is acceptable. SMARM is
|
||||
not a real-time scheduler.
|
||||
|
||||
Known failure mode: tight no-alloc loops are invisible to this mechanism. Actors doing sustained
|
||||
allocation-free compute must call `loom::yield_now()` explicitly, or offload to a thread pool
|
||||
allocation-free compute must call `smarm::yield_now()` explicitly, or offload to a thread pool
|
||||
outside the actor scheduler (e.g. rayon). This is documented and acceptable — such loops are rare
|
||||
in message-passing workloads.
|
||||
|
||||
@@ -99,12 +99,12 @@ An actor yields at:
|
||||
- **Channel send/recv** — the primary communication primitive
|
||||
- **Mutex contention** — attempting to lock a held `Arc<Mutex<>>` parks the actor
|
||||
- **IO** — blocking on a socket or file descriptor parks the actor until the IO thread signals readiness
|
||||
- **`loom::sleep(duration)`** — parks the actor; the timer wheel re-queues it on expiry
|
||||
- **`loom::yield_now()`** — explicit cooperative yield
|
||||
- **`smarm::sleep(duration)`** — parks the actor; the timer wheel re-queues it on expiry
|
||||
- **`smarm::yield_now()`** — explicit cooperative yield
|
||||
- **Allocator preemption** — as above
|
||||
- **Spawn** — does not yield by default; the new actor is queued and the spawner continues
|
||||
|
||||
`std::thread::sleep` inside an actor blocks the entire OS thread and should never be used. Loom
|
||||
`std::thread::sleep` inside an actor blocks the entire OS thread and should never be used. SMARM
|
||||
may emit a warning if it can detect this.
|
||||
|
||||
### IO thread
|
||||
@@ -112,7 +112,7 @@ may emit a warning if it can detect this.
|
||||
A single dedicated IO thread runs an `epoll`/`kqueue` loop. Actors blocking on IO register their
|
||||
file descriptor and PID; the IO thread moves them back into the global queue when the fd is ready.
|
||||
A `HashMap<RawFd, Pid>` maps fds to parked actors. Cancellation (actor dies while waiting on IO)
|
||||
deregisters the fd. This is intentionally simple and not pluggable; Loom is not a general async
|
||||
deregisters the fd. This is intentionally simple and not pluggable; SMARM is not a general async
|
||||
executor.
|
||||
|
||||
### Communication
|
||||
@@ -155,7 +155,7 @@ sensible global default.
|
||||
|
||||
### Mutex timeout
|
||||
|
||||
Every `loom::mutex` lock attempt is mediated by the scheduler. If the lock is not acquired within
|
||||
Every `smarm::mutex` lock attempt is mediated by the scheduler. If the lock is not acquired within
|
||||
a configurable timeout, the actor receives a `LockTimeout` error rather than parking forever. This
|
||||
is a hard runtime guarantee, not a convention. Default timeout is global and configurable;
|
||||
individual locks and individual call sites can override it.
|
||||
@@ -165,9 +165,9 @@ individual locks and individual call sites can override it.
|
||||
Actors can spawn children and wait on a group of handles:
|
||||
|
||||
```rust
|
||||
let h1 = loom::spawn(|| compute_a());
|
||||
let h2 = loom::spawn(|| compute_b());
|
||||
let (a, b) = loom::join!(h1, h2);
|
||||
let h1 = smarm::spawn(|| compute_a());
|
||||
let h2 = smarm::spawn(|| compute_b());
|
||||
let (a, b) = smarm::join!(h1, h2);
|
||||
```
|
||||
|
||||
`join!` parks the calling actor until all handles complete. The last child to finish re-queues the
|
||||
@@ -176,7 +176,7 @@ parent. This is a countdown in the parent's descriptor; no polling, no waker reg
|
||||
|
||||
### Timer wheel
|
||||
|
||||
`loom::sleep` and supervision timeouts are driven by a timer wheel in the scheduler. Sleeping
|
||||
`smarm::sleep` and supervision timeouts are driven by a timer wheel in the scheduler. Sleeping
|
||||
actors are parked and re-queued by the timer thread on expiry. The timer wheel is internal
|
||||
infrastructure; its design is an implementation detail.
|
||||
|
||||
@@ -189,22 +189,29 @@ infrastructure; its design is an implementation detail.
|
||||
- **Queue contention** — if `Mutex<HashMap>` proves to be a bottleneck under profiling, evaluate
|
||||
`DashMap` or a lock-free work-stealing deque (e.g. `crossbeam-deque`). Not before.
|
||||
- **AVX-512 context save** — extend `ContextSaveArea` when there is a concrete use case.
|
||||
- **`loom::sleep` vs raw sleep semantics** — further control knobs deferred until the basic sleep
|
||||
- **`smarm::sleep` vs raw sleep semantics** — further control knobs deferred until the basic sleep
|
||||
is working and real use cases are understood.
|
||||
- **Supervision tree API** — the contract is defined; the recursive hierarchy, restart strategies,
|
||||
and introspection API are implementation work.
|
||||
- **no_std support** — the assembly shim is no_std friendly but the IO thread and allocator require
|
||||
OS primitives. Target is no_std + `alloc` on hosted platforms; bare metal is out of scope.
|
||||
- **Distribution** — Loom is a single-process runtime. No distribution protocol, no BEAM-style
|
||||
- **Distribution** — SMARM is a single-process runtime. No distribution protocol, no BEAM-style
|
||||
clustering.
|
||||
|
||||
---
|
||||
|
||||
## What Loom is Not
|
||||
## What SMARM is Not
|
||||
|
||||
- Not a drop-in replacement for Tokio. Loom does not implement `Future` or the async executor interface.
|
||||
- Not a general allocator. Loom manages actor stacks; heap allocation for actor data goes through
|
||||
- Not a drop-in replacement for Tokio. SMARM does not implement `Future` or the async executor interface.
|
||||
- Not a general allocator. SMARM manages actor stacks; heap allocation for actor data goes through
|
||||
the system allocator.
|
||||
- Not Erlang. No hot code reloading, no distribution protocol, no BEAM bytecode. Loom is a
|
||||
- Not Erlang. No hot code reloading, no distribution protocol, no BEAM bytecode. SMARM is a
|
||||
concurrency runtime, not a platform.
|
||||
- Not a real-time scheduler. Timeslice accuracy is best-effort.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## On names
|
||||
|
||||
<sub>The name is a recursive acronym. The M is for Marks, as in the BEAM — Bogdan/Björn's Erlang Abstract Machine, the virtual machine that runs Erlang and Elixir. smarm is not the BEAM. It just admires it from a safe distance.</sub>
|
||||
1297
docs/smarm - Deep Dive.html
Normal file
1297
docs/smarm - Deep Dive.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user