ADR-121: The Ring’s Park Hears the Bell, So Every Worker Operation Can Wake the Executor

Status: Accepted Date: September 16, 2026 Related: ADR-303 D3 and D4 (I/O split by kind; the runtime runs before the first statement), ADR-055 D3 (the executor is ours and drives the I/O that is already there), ADR-019 (standard input), Part III 13.3b (io-method)

1. Context

The runtime finishes an operation in one of two ways. On a machine with a completion queue the kernel does the read and the ring reports it; where the queue is missing or forbidden, a worker thread does it and rings a bell — a count the executor’s park hook watches. The two paths serve the same operations, and without this record they differ in one thing: while the executor parks on the ring, it waits on the ring’s own count of outstanding jobs, and a worker’s reply is not one of them. A worker’s reply then cannot wake a ring park, and no future may be fed from the worker path: block_on spins where main is alone and panics otherwise, about a waker nobody arranged.

Two things rest on it. Standard input is a worker operation, so io::read, io::read_to_string and io::lines could only block the thread they run on. And rt::io::wait, the readiness half of the runtime built for sockets, could be blocked on and not awaited — and it is the piece an HTTP server needs first.

Wiring standard input to readiness would not help: the defect is the park, not the input.

2. Decisions

D1 — The ring carries an eventfd, always armed, and the bell writes to it.

Where the executor parks on the ring, one file descriptor is registered on the ring with a read always in flight. ring_the_bell writes to it. A worker’s reply therefore completes a ring job, the park returns, and the executor runs whatever the worker’s reply made ready. The two paths stop differing in what they can feed: every worker operation is awaitable — standard input, socket readiness, and whatever a later operation carries.

D2 — The bell’s completion is not mistaken for an operation’s.

The eventfd read carries its own user data, is answered by re-arming it, and is never handed to a future as a result. The count of outstanding jobs the park consults excludes it, so a park with only the bell armed still sleeps.

D3 — A hang is the failure this may not have, so it is a panic and a test.

The worst failure a runtime can have is a park nobody wakes. park’s own false is a loud panic rather than a wait; this keeps that standard. A test holds that a worker reply wakes a ring park, and that a future fed from a worker completes under block_on with other tasks alive; a test of a hang bounds itself, because a hang is the failure this may not have applies to the test that reports it.

D4 — Standard input stays on the worker path, and becomes awaitable through it.

No second read shape for standard input, no ring path for a stream that has no size to stat. io::read, io::read_to_string and io::lines suspend because the path they were always on can now wake the executor. The same is true of rt::io::wait, which is what the server’s socket layer awaits.

3. Consequences

4. What this does not decide

5. How the bell is held to D1-D3

This site is open source. Improve this page.