Scheduler.deinit closes the bounded leaks B1 documented: it reaps any leftover
ready fibers, frees every heap Task from go (now tracked via a task_allocs
field), frees the timers/io_waiters/task_allocs List backings, and closes the
lazily-opened kqueue fd. Terminal + idempotent; the per-spawn/go closure env
remains unfreeable (language limitation). Locked by
examples/concurrency/1820-concurrency-fiber-scheduler-deinit.sx, which exercises
every freed resource under a tracking GPA (freed by deinit: 5, kq reset to -1).
Also converts plain-struct '= ---'+field-assign init to '.{ ... }' literal init
where '---' carries no meaning: Scheduler.init, Dock.make, and the fiber
examples 1811/1813/1814/1816 (partial literals zero-fill the index-filled array
fields). Unions, '---'-feature tests, the 0154 regression, documented
generic-pack gaps, and loop/conditional inits are intentionally left on '---'.
UFCS generic overload resolution (issue 0157 follow-ups):
- P1-a: call planning (calls.zig) used the last-wins fn_ast_map winner
while lowering reselected by receiver, so the planned result type
could disagree with the dispatched function and misbox the result.
Both now share selectUfcsGenericByReceiver(.., fd0).
- P1-b: selection scanned module_decls globally, flagging a
transitively-hidden same-named overload as a false ambiguity. Now
two-tier: directly-visible authors first (ambiguity only among
those), global fallback for receiver-reachable namespaced methods
(e.g. Task.cancel) that defers to fd0 on a hidden tie.
- P2-b: boolean specificity tied *$T with *Box($T). Now peels pointer
layers so the structurally-narrower receiver wins.
Scheduler (sched.sx):
- P1-c: a second concurrent Task.wait overwrote the single waiter slot
-> silent deadlock. Now one-awaiter-per-task loud abort.
- P2-c: sleep(negative) rewound the monotonic virtual clock. Rejected
loudly.
(P2-a, non-generic-winner-hides-generic, did not reproduce -- the
non-generic arm already falls through.)
Regressions: examples/generics/0218 (receiver specificity +
plan/lowering agreement), examples/concurrency/1818 (negative-sleep
abort), 1819 (double-wait abort). Suite green 758/0.
A fiber can block on a file descriptor and the run loop blocks on
kevent until the kernel reports it ready. Reuses the existing
std/net/kqueue.sx bindings. Scheduler gains a lazy kq fd + an
io_waiters list; block_on_fd arms a one-shot EVFILT_READ registration,
records an IoWaiter, and suspends. Run-loop Mode 2: when the ready
queue drains and no timer is pending, block on kq_wait(-1), match each
fired ident to its waiter, evict it, wake the fiber. wake evicts a
pending fd-waiter (cancel_io_waiter_for) so no stale IoWaiter outlives
a reaped fiber.
Adversarial review found two CRITICALs: (1) two fibers on the same fd
share one kqueue registration (macOS EV_ADD replaces), so one is lost
and the loop hangs -- fixed by enforcing one-waiter-per-fd with a loud
abort; (2) an fd-waiter on a never-ready fd 'hangs' -- reclassified as
correct event-loop semantics (a server idling on a socket), with the
misleading orphan-check comment corrected. UAF parity, ident width,
EINTR handling, timer/io precedence all probed safe.
Example: 1816 (pipe roundtrip -- reader blocks, writer writes, reader
wakes via kqueue). macOS only; linux epoll twin deferred. Suite green 754/0.
Add a virtual clock + sleep timers to the M:1 scheduler so fibers
schedule in reproducible simulated time. Scheduler gains clock_ms (the
virtual clock, advances only as timers fire), a timers list, now_ms(),
sleep(ms) (arm {clock_ms+ms, current} + suspend), and a timer-driven
run (drain ready -> fire earliest timer -> advance clock -> wake ->
repeat; the orphan-suspend deadlock check is preserved for a genuine
no-timer park). Wakes fire in deadline order with a FIFO tiebreak.
Adversarial review found a use-after-free: a fiber woken early (manual
or Task wake) before its sleep timer fired was reaped while its Timer
kept a dangling *Fiber, so a later fire dereferenced freed memory.
Fixed: wake evicts the fiber's pending timer (cancel_timer_for) -- every
re-ready path funnels through wake, so no stale timer outlives its fiber.
Examples: 1814 (sim-timer deadline ordering), 1815 (early-wake timer
eviction regression). Suite green 753/0.
library/modules/std/sched.sx: a generic Fiber + Scheduler over the
proven naked swap_context on guarded mmap stacks --
init/spawn/yield_now/suspend_self/wake/run (B1.5a), then Task($R) +
go/wait/cancel, a truly-suspending nullary-thunk async layer (B1.4a).
go(work) runs a thunk as a real fiber; wait() parks the caller until it
completes. Self-contained in sched.sx (io.sx importing it would
duplicate the _fib_tramp global asm).
Hardened per adversarial review: wake guarded on .suspended (FIFO
corruption), suspend_self/yield_now guard a null current, loud
mmap/mprotect/OOM/deadlock bails, cancel skips not-yet-run work.
Closure-env + heap-Task leaks documented (bounded, default-GPA-invisible).
Examples: 1811 (round-robin), 1812 (suspend/wake + spurious-wake guard),
1813 (async interleave + await-suspend + cancel). Also files issue 0155
(scalar-pointer index panics codegen -- non-blocking, found in review).