- owns the framework ledger's three seam rows; deadlines compose with the arc's park plane (POLL_ADD+TIMEOUT fork recorded); framework knobs explicitly out of scope; stalled-client soak in acceptance - board + table rows (held seqs bumped); pairs naturally with 24 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.1 KiB
| iteration | status |
|---|---|
| 35 | refine |
Iteration 35 — net runtime seams: timeouts, Unix sockets, peer address
Format:
product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.Inserted 2026-08-22 — the framework ledger's three 🔧 rows get one owner: "Read/write/idle timeouts —
nethas no timeout surface", "Unix socket binding —net.listenis TCP-only", and "Trusted-proxy client IP — needs a peer-address runtime seam". Each is a small builtin-surface addition; the framework knobs built ON them stay framework slices. Off the concurrency chain; no chain item depends on it, but a production-shaped deployment (proxy in front, sockets not ports, slow-client defense) needs all three.
Why this iteration exists
The framework cannot defend against a slow client (no read deadline —
one stalled socket parks a fiber forever), cannot sit behind a
same-host proxy the idiomatic way (Unix socket beats a loopback port),
and cannot TRUST X-Forwarded-For (parsing is expressible in .wo
today, but verifying the peer actually IS the proxy needs the peer's
address, which no builtin exposes). All three are runtime seams by
nature: the information or mechanism lives at the fd level.
Goals
- Deadlines on parked net I/O. A read/accept/write that would park can carry a deadline; expiry resumes the fiber with a distinguishable timeout result (nil-or-trap decided by the spec, consistent with the stdlib's nil-vs-trap contracts). On serving shards this composes with the existing plane — a park is ALREADY a POLL_ADD or TIMEOUT submission (arc T4); the seam arms both and takes whichever fires. Program mode gets the same surface over blocking syscalls.
- Unix-domain listeners and connections beside the TCP ones — same accept/read/write/close builtins afterward (an fd is an fd; only the bind/connect shape differs).
- The peer's address, readable — for an accepted connection, enough
to answer "is this my trusted proxy?" (address + family; port where
meaningful). The framework's trusted-proxy middleware then becomes a
pure-
.wocandidate slice. - The framework knobs are explicitly NOT here — timeout defaults, proxy allowlists, socket-path config all live in framework slices that consume these seams.
Acceptance Criteria (draft — the spec refines)
- Given a fiber reading a socket whose peer sends nothing, when the declared deadline expires, then the fiber resumes with the timeout result (never a hang), other fibers having run throughout (TID-verified), and the fd is still usable or cleanly closed per the spec's stated semantics.
- Given a listener on a Unix socket path, when a client connects and exchanges bytes, then the whole existing net surface works unchanged over it, and the log-watcher/web-app gates stay byte-identical on TCP.
- Given an accepted connection, when the handler asks for the peer address, then loopback TCP and Unix-socket peers are both identifiable, and the answer round-trips into the trusted-proxy check's comparison.
- Given the full battery plus a soak with deliberately stalled clients, when it runs, then zero leaked fds and flat RSS — timeouts must CLEAN UP, not merely return.
Out Of Scope
- Framework policy (default timeout values, proxy allowlist shape, keep-alive idle policy) — framework slices on top.
- TLS, h2c — unchanged owners (proxy; parked behind 23).
- Connect-side timeouts for outbound clients beyond what the deadline seam gives free — no workload asks yet.
- Cancellation as a general mechanism — iteration 31's request/response + timers own actor-level cancellation; this is strictly fd-level deadlines.
Info
Forks the spec must settle:
- Timeout result shape: nil result vs a distinguishable trap —
must follow the stdlib's existing nil-vs-trap doctrine
(
07-series contracts; a timeout is an EXPECTED outcome, which argues nil). - Deadline plumbing on the plane: one park may need BOTH a
POLL_ADD and a TIMEOUT in flight (io_uring linked ops vs two
submissions + first-wins cancel; epoll fallback = the existing
deadline scan). The park protocol contract
(
03-concurrency-coroutines.md) gains the rule. - Surface shape: per-call deadline argument vs per-fd setting
(
net.set_deadline(fd, ms)); leaning per-call — no hidden fd state, matches the no-coloring doctrine. - Unix-socket path semantics: unlink-before-bind? stale-socket handling on restart (the never-stopping-runtime doctrine says a restart must not need manual cleanup).
Proposed Solution
Brainstorm → small spec settling the four forks → implement in the
time.ticks/34 shape (builtin ids + park.c deadline arming + contract
rows + fixtures incl. a stalled-client corpus case). Independent of the
chain; natural pairing is right before or with iteration 24 (chat wants
read deadlines for dead-client eviction even before lifecycle timers).