Iteration 24 closes, absorbing 31 and 34. No code in this commit. - stories 24, 31, 34 -> `status: done`, each with a landing banner. 24's records the gate numbers and BOTH disclosed deviations: monitor takes three arguments (the caller may be `main`, which has no mailbox) and a v1 `call` reply is a typed scalar (which is what let the agreement be checked at compile time, WO-E226). 31's notes it landed INSIDE 24 and that a fifth mechanism it never anticipated came out of proving the gate — the drain guarantee (40). 34's names the gap it did NOT close: still no RNG, so CSRF/sessions stay blocked - board: in-progress row cleared, marker doc deleted (convention), the standup entry in the six-question shape, chain note — next link is databasev2 4 (io_uring group-commit, chain 5) - graph: PUBSUB2 (pub/sub + WebSockets, "rejected until here") -> done - porch ledger: a WebSocket/pub-sub row added; the cancellation row now says what it actually waits on rather than repeating "the arc"; the README's "no WebSockets/SSE" limitation was stale — WebSockets are supported, SSE and chunked encoding are not - CODE-LOGIC: runtime/src gains the actor-lifecycle section (call, death, the cap counter's sender/home-thread split, the monitor walk, the timer list), the drain guarantee, and the digest section; docs/examples/chat gains its own — actor topology, WHY two actors per connection, fd ownership, and the shutdown choreography Battery after the doc edits: wovm-test 36 suites 0 fail, woc-test exit 0, oop-e2e 119/0, chat 11/0, web-app 46/0, linkcheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.8 KiB
| iteration | status | chain |
|---|---|---|
| 31 | done | 3 |
Iteration 31 — actor lifecycle: request/response, backpressure, death, timers
Format:
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.Inserted 2026-08-21 (concurrency-chain re-sequence; the iteration was named as "new 31" in the 2026-08-20 code-review re-sequence — this is its story file). Third in the chain, stage 3 → 22 → 31 → 24 → 23 → 32: chat (iteration 24) cannot be written honestly without these four mechanisms.
✅ LANDED 2026-08-27 — INSIDE 24, per the 2026-08-23 directive that absorbed it. All four mechanisms shipped:
call/reply with a typed scalar reply (id 88, WO-E226), bounded mailboxes (WO_MAILBOX, default 1024, fail-fast with a catchableWO_T_ACTOR), actor death that traps callers instead of hanging them,monitor(id 89) andtime.after(id 90). Ids 89 and 90 were reserved holes inwob.h; they are filled.A fifth mechanism was added that this story did not anticipate: the shutdown drain guarantee, 40. It is lifecycle semantics — this story gave actors a death notice, 40 gives the program a shutdown that does not lose mail — and it was found by measurement while proving 24's gate, not by review.
How each piece works:
runtime/src/CODE-LOGIC.md, "Actor lifecycle".
Why this iteration exists
The arc's stages 1+2 shipped spawn/send mechanism without lifecycle:
send is one-way and callers sleep-poll to await an answer; the
mailbox FIFO grows without bound (a hot sender can exhaust a shard's
memory); an actor that traps dies silently (nobody learns, nothing
restarts, its mailbox rots); and there is no timer surface beyond a
fiber blocking in time.sleep. Every real serving program — chat first —
is made of request/response turns, bounded queues, death notices, and
deadlines. Without this iteration the arc is a demo, not a runtime.
Goals
- Request/response over one-way sends. A caller can send and park
until the reply arrives — one surface, no
sleep-polling, no second concurrency vocabulary. Ownership rules unchanged: the request moves, the reply moves back. - Bounded mailboxes with a stated backpressure policy. A mailbox has a cap; what happens at the cap (park the sender vs error) is decided by the spec, one policy, doctrine-pure — no silent unbounded growth anywhere in the runtime.
- Actor death is observable. A trap or normal exit produces a signal
another actor can receive; a fiber-trap already isolates (stage 1) —
this makes the fact of death deliverable, so a supervisor CAN be
written in
.wo. - Timers as messages. A deadline or interval delivers to a mailbox like any other send, riding the shard's existing io_uring/epoll timeout plumbing (arc T4) — no new event loop.
Acceptance Criteria (draft — the spec refines)
- What to achieve?
- Given an actor that answers requests,
- when a caller awaits the reply,
- then the caller's fiber parks (the shard serves other fibers, TID-verified), resumes with the moved reply, and never busy-waits.
- What to achieve?
- Given a mailbox at its cap,
- when another send arrives,
- then the stated backpressure policy fires deterministically, memory stays bounded (RSS flat under a hot-sender soak), and no message is silently dropped.
- What to achieve?
- Given an actor that traps mid-message,
- when it dies,
- then its drop maps run (ASan zero leaks), a death signal
reaches the observer that asked for one, and a supervisor written
in
.wocan respawn it.
- What to achieve?
- Given a timer armed by an actor,
- when it fires,
- then the actor receives it as an ordinary message on its own shard, and cancelling before expiry means it never delivers.
Out Of Scope
- Supervision TREES / OTP-scale restart policy — a
.wolibrary once death signals exist, not runtime policy. - Priorities and custom scheduling — the reduction budget stays the only fairness mechanism.
- Distributed (cross-process) supervision — no network layer exists.
- Changing the ownership-move rule or the unified address surface — iteration 8's decisions stand.
Info
Forks the spec must settle:
- The request/response surface. A reply-address baked into the message shape vs a runtime-level call that parks — and what the compiler checks (does a request type name its reply type?).
- The backpressure policy at the cap. Park the sender (natural with fibers, risks deadlock cycles) vs fail the send (explicit, pushes handling to the program). One policy, stated; not configurable per mailbox in v1.
- The death-signal shape. Erlang's link (bidirectional, dies together) vs monitor (one-way notice) — likely monitor-only v1.
- The timer surface. Builtin (
time.afterdelivering a message) vs actor-spawned sleeper fiber — and cancellation semantics.
Sources: the 2026-08-20 code-review findings (the gaps this iteration
answers), the arc plan's stage-2 deviations
(2026-08-20-shard-fiber-arc.md
— the unbounded FIFO is deviation 4's mutex inbox), and BEAM precedent
already surveyed in
docs/plan/exploration/fibers/00-fibers.md.
Proposed Solution
Brainstorm → spec → plan after the arc's stage 3 lands and iteration 22 has its baseline (the mailbox-cap and inbox-ring decisions want 22's mutex number). The spec is written against iteration 24's needs — chat names the lifecycle mechanisms it consumes, this iteration names chat as its first honest consumer.