prompt cancellation via all-fibers registry + adoption guide
interruptTask now walks an intrusive all-live-fibers list, reaching
fibers parked on io readiness, futex, timers, or await alike — the
'cancel lands on next natural wakeup' gap is closed. new test: cancel
a fiber parked in netRead on a silent socket; must return promptly
with error.Canceled. 16/16 on Debug+ReleaseSafe x macos+linux.
docs: adoption-guide.md — general guide for swapping Io.Threaded for
zio.ZioBackend in any project (zlay as worked example): when to adopt,
the swap, what-runs-where table, the four domain rules, gotchas
(readSliceShort, close-as-wakeup, shutdown order), rollout discipline,
honest maturity status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prompt cancellation via all-fibers registry + adoption guide
interruptTask now walks an intrusive all-live-fibers list, reaching
fibers parked on io readiness, futex, timers, or await alike — the
'cancel lands on next natural wakeup' gap is closed. new test: cancel
a fiber parked in netRead on a silent socket; must return promptly
with error.Canceled. 16/16 on Debug+ReleaseSafe x macos+linux.
docs: adoption-guide.md — general guide for swapping Io.Threaded for
zio.ZioBackend in any project (zlay as worked example): when to adopt,
the swap, what-runs-where table, the four domain rules, gotchas
(readSliceShort, close-as-wakeup, shutdown order), rollout discipline,
honest maturity status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fiber scheduler v1 + live demo
zio.Sched: blocking-style io (read/writeAll/connectIp4/accept/sleepMs) for
fibers multiplexed on one os thread over the poller. one-shot event arming,
generation-tagged lazy timer heap, every park spurious-wakeup safe (io ops
retry; sleep re-parks until deadline; connect re-checks via getpeername),
finished fibers recycled through a free list so stale poller tokens can
never dereference freed memory.
two lessons banked along the way:
- fresh fiber frames must terminate the unwinder chain: fp=0 AND lr=0
(aarch64) / null return-address slot (x86_64). debug allocators capture
stack traces on alloc-from-fiber; a stale lr sent the unwinder into
garbage (segv at 0x8) only in tests that allocate on fiber stacks.
- never rely on closing an fd to wake a parked fiber — kqueue drops
filters on close and the wakeup is silently lost. shutdown paths use
timed waits (acceptTimeout / waitReadableTimeout) instead.
- x18 clobber is now os-conditional in the vendored switch: required
where allocatable (linux), warning-and-meaningless where reserved
(darwin/windows).
demo (bench/fiber_demo.zig, zig build demo): N tcp echo sessions, all
blocking-style, single thread. linux ReleaseSafe: 4,002 fibers,
~200k round-trips/s, os-threads=1.
10/10 tests, Debug + ReleaseSafe, macos + linux.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
backend: fiber-backed std.Io vtable splice
zio.ZioBackend wraps Io.Threaded, copies its vtable, and overrides:
- concurrent/await/cancel: tasks run as fibers on the sched loop; await
parks fibers or futex-blocks foreign threads (refcounted task, result
copied under acquire/release ordering); cancel wakes parked fibers via
cross-thread interrupt injection
- netConnectIp/netAccept/netRead/netWrite: non-blocking + park on
readiness; accept flips Threaded-created listeners non-blocking
- sleep: sched timer heap for fiber callers
- futexWait/futexWake: fiber-aware futex table + always-both-domains
wake — Io.Mutex/Condition/Queue now work for fiber callers unchanged
(proven by a fiber-vs-pool-thread mutex contention test)
- netLookup: spilled to an inner-Threaded thread; the calling fiber
parks in Queue.get via the fiber-aware futex, so DNS never stalls
the loop
sched grows the cross-thread machinery: self-pipe wake, injection
queue (spawn/futex-wake/interrupt), runForever, parkCurrent/sleepUntil,
gen-tagged futex waiter list.
lesson banked: a 'hang' during bring-up was the test using
readSliceShort as a message-read — it parks until the buffer fills
(short only at stream end). diagnosed end-to-end with lskq (live
kqueue knote dump): knotes armed, no data pending -> writer-side
protocol bug, not a runtime bug.
15/15 tests: Debug + ReleaseSafe, macos + linux.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>