fibers: Scheduler.deinit + struct-literal init cleanup
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 '---'.
This commit is contained in:
@@ -4,7 +4,35 @@ Companion to [PLAN-FIBERS.md](PLAN-FIBERS.md). Update after every step (one step
|
||||
per the cadence rule). New corpus category: `18xx` concurrency.
|
||||
|
||||
## Last completed step
|
||||
**B1.5 — END-TO-END M:1 validation — STREAM B1 COMPLETE.** A single capstone exercises the whole
|
||||
**B1 follow-up — `Scheduler.deinit` (close the bounded leaks).** Post-B1 non-blocking cleanup: a
|
||||
terminal `deinit` on `library/modules/std/sched.sx`'s `Scheduler` releases the resources B1 documented
|
||||
as leaked. Frees, in order: (1) any fibers still enqueued ready (leak-safety net for `spawn`/`go`
|
||||
without `run()` — `munmap` stack + free struct; a suspended off-queue fiber is unreachable, but a clean
|
||||
`run()` aborts on orphans so none survive it); (2) every heap `*Task` from `go` — newly tracked via a
|
||||
`task_allocs: List(*void)` field appended in `go` (the scheduler otherwise has no handle on its generic
|
||||
`Task($R)`s); (3) the three `List` backings (`task_allocs`/`timers`/`io_waiters`, all grown through
|
||||
`own_allocator`); (4) the lazily-opened kqueue fd (`close`, reset to `-1`). NOT freed (unchanged
|
||||
language limitation): the per-`spawn`/`go` closure env (sx exposes no env-free). Idempotent (rests on
|
||||
`List.deinit` nulling `items` + the `kq`/`ready_head` resets); TERMINAL contract — no scheduler-owned
|
||||
handle (`*Task`, `*Fiber`, the scheduler) is usable after `deinit`.
|
||||
- Added a canonical `close :: (i32) -> i32 extern libc` (matches the dedupe-canonical signature 1816
|
||||
already uses) + the `task_allocs` field.
|
||||
- Locked by `examples/concurrency/1820-concurrency-fiber-scheduler-deinit.sx` (aarch64-macOS `.build
|
||||
{"target":"macos"}`, runs end-to-end): one run touches every freed resource — a SLEEPER (`timers`), a
|
||||
pipe READER `block_on_fd` + WRITER (kqueue fd + `io_waiters`), two `go` tasks (`Task`s + `task_allocs`)
|
||||
— then `deinit`. Verified by a tracking `GPA`: `freed by deinit: 5`, `live after deinit: 5` (the
|
||||
RESIDUAL = the 5 documented closure envs, not a bug), `kq open after run: true` → `kq after deinit:
|
||||
-1` (the genuinely-open kqueue fd is closed), `read: 3 [97 98 99]` (the fd path actually ran). Counts
|
||||
captured into locals BEFORE printing (`print` allocates format temporaries through the same GPA).
|
||||
- **Adversarially reviewed (worker):** no real memory-safety bug in the supported (deinit-after-`run`)
|
||||
path — reap-loop reads `f.next` before freeing `f`, the three freed List backings + Tasks + kq are all
|
||||
disjoint + scheduler-owned, no over-free, idempotent. The one CRITICAL it raised was a DOC contradiction
|
||||
(step-(1) defensive reap vs step-(2) "post-run only"), reconciled by spelling out the terminal contract.
|
||||
Its 0154-over-store concern (`.{}`→`List` writes in `init` could clobber `kq`) was PROBED and cleared:
|
||||
`kq == -1` immediately after `init`, all fields clean. Suite GREEN **759/0**.
|
||||
|
||||
### Earlier — B1.5 — END-TO-END M:1 validation — STREAM B1 COMPLETE
|
||||
A single capstone exercises the whole
|
||||
colorblind pure-sx async runtime together: the M:1 scheduler (B1.5a) + suspending fiber-task async
|
||||
`go`/`wait` (B1.4a) + deterministic virtual-time `sleep`/`now_ms` (B1.4b), over the `abi(.naked)`
|
||||
`swap_context` on guarded `mmap` stacks (B1.0–B1.3). `examples/concurrency/1817-concurrency-fiber-m1-end-to-end.sx`:
|
||||
@@ -366,11 +394,14 @@ async (B1.4a: `Task($R)`/`go`/`wait`/`cancel`), deterministic virtual-time timer
|
||||
`clock_ms`/`now_ms`/`sleep`, timer-driven `run`), AND real fd readiness via kqueue (B1.4c: lazy `kq`,
|
||||
`io_waiters`, `block_on_fd`, run-loop Mode 2) — all over the `abi(.naked)` `swap_context` on guarded
|
||||
`mmap` stacks (B1.0–B1.3), reusing `std/net/kqueue.sx`. Every park path (timer sleep, fd block, raw
|
||||
suspend) is balanced through `wake` (which evicts stale timer + fd waiters — the UAF guards). Locked
|
||||
by `18xx` 1800–1817 (naked-asm, context-snapshot, blocking async, the switch + §10.7 stress gate +
|
||||
guarded stacks + Win64 sibling, scheduler round-robin, suspend/wake, async go/wait/cancel, sim-timer
|
||||
ordering, timer early-wake eviction, kqueue pipe I/O, and the **1817 end-to-end capstone**). Suite
|
||||
GREEN **755/0**, master committed.
|
||||
suspend) is balanced through `wake` (which evicts stale timer + fd waiters — the UAF guards). A terminal
|
||||
`deinit` (B1 follow-up) closes the previously-documented leaks: heap `Task`s (tracked via `task_allocs`),
|
||||
the `timers`/`io_waiters`/`task_allocs` List backings, and the kqueue fd; the per-`spawn`/`go` closure
|
||||
env remains unfreeable (language limitation). Locked by `18xx` 1800–1820 (naked-asm, context-snapshot,
|
||||
blocking async, the switch + §10.7 stress gate + guarded stacks + Win64 sibling, scheduler round-robin,
|
||||
suspend/wake, async go/wait/cancel, sim-timer ordering, timer early-wake eviction, kqueue pipe I/O, the
|
||||
**1817 end-to-end capstone**, sleep-negative/double-wait guards, and **1820 scheduler-deinit**). Suite
|
||||
GREEN **759/0**, committed.
|
||||
|
||||
Future work (none blocking B1): a **linux epoll twin** of `block_on_fd` (mirror via `std/net/epoll`;
|
||||
OS-neutral facade `std.event`) — B1.4c wired macOS kqueue only; routing the suspending async through
|
||||
@@ -471,11 +502,13 @@ fibers/Io/scheduler code yet. Grounded floor facts:
|
||||
|
||||
## Next step
|
||||
**Stream B1 is COMPLETE — no next step in this stream.** The pure-sx M:1 async runtime is feature-
|
||||
complete and committed (1800–1817 green, 755/0). Pick up **Stream B2** (channels / structured cancel /
|
||||
async stdlib) as a fresh carve (PLAN-CHANNELS.md), OR one of the documented non-blocking follow-ups:
|
||||
the linux `epoll` twin of `block_on_fd`, a `Scheduler.deinit` (free the kq fd / heap Tasks / drain
|
||||
leaks), `Future(void)`/`timeout` (needs issue 0150), or routing the suspending async through the
|
||||
erased `context.io` for the M:N model. None of these block B1.
|
||||
complete and committed (1800–1820 green, 759/0), now WITH a `Scheduler.deinit` closing the bounded
|
||||
leaks. Pick up **Stream B2** (channels / structured cancel / async stdlib) as a fresh carve
|
||||
(PLAN-CHANNELS.md), OR one of the remaining non-blocking follow-ups: the linux `epoll` twin of
|
||||
`block_on_fd`, `Future(void)`/`timeout` (needs issue 0150), or routing the suspending async through the
|
||||
erased `context.io` for the M:N model. (`Scheduler.deinit` — DONE, see Last completed step.) None of
|
||||
these block B1. The closure-env leak survives `deinit` (no language affordance to free a closure env);
|
||||
revisit if/when sx grows closure-env ownership.
|
||||
|
||||
**Deferred (future B1.4c sibling): the linux epoll twin of `block_on_fd`.** B1.4c wired the **macOS
|
||||
kqueue** path only (the host is aarch64-macOS). The linux mirror would register interest via
|
||||
@@ -521,6 +554,14 @@ incomplete); a dedicated effort; lambda workers are the idiom meanwhile.
|
||||
codegen (`.unresolved` reaching LLVM emission, `src/backend/llvm/types.zig:196`). Found in the
|
||||
B1.5a review; the scheduler doesn't use it (array-field index + `.*` only). Filed for its own
|
||||
session: `issues/0155-scalar-pointer-index-llvm-panic.{md,sx}`.
|
||||
- **✅ issue 0158 — FIXED** — a plain `union` struct-literal (`b : Overlay = .{ f = 3.14 }`) fell
|
||||
through the generic struct-literal path (`getStructFields` empty for a union → malformed
|
||||
`structInit`, overlapping zero-fill clobbered the member → silent `0.0`). Fix: `lowerStructLiteral`
|
||||
detects a plain-union target → new `lowerUnionLiteral` (`src/ir/lower/stmt.zig`) writes each named
|
||||
member into a union-sized slot via the assignment-path lvalue resolver, then loads it back.
|
||||
Single-arm only (one direct member, or same-arm promoted members); overlapping/different-arm/
|
||||
positional literals are diagnosed. specs.md updated. Regressions: `examples/types/0194` +
|
||||
`examples/diagnostics/1191`.
|
||||
- **✅ issue 0157 — FIXED** (B1.4a) — a user generic `ufcs` method whose name collides with a
|
||||
stdlib re-export resolved via last-wins `fn_ast_map` with no receiver filtering → wrong overload →
|
||||
`$R` unbound → LLVM panic. Fix: `selectUfcsGenericByReceiver` (`src/ir/lower/call.zig`) — most
|
||||
@@ -593,6 +634,17 @@ incomplete); a dedicated effort; lambda workers are the idiom meanwhile.
|
||||
trusted. `18xx` asserts program-emitted ordering contracts, not raw interleaving.
|
||||
|
||||
## Log
|
||||
- **B1 follow-up — `Scheduler.deinit`.** Closes the bounded leaks B1 documented. Added a `task_allocs:
|
||||
List(*void)` field (appended in `go` so the scheduler can reach its generic `Task($R)`s) + a canonical
|
||||
`close` extern, then a terminal idempotent `deinit`: reap leftover ready fibers (`munmap` + free) →
|
||||
free tracked Tasks → `List.deinit` the 3 backings → `close` the lazy kqueue fd (reset `-1`). Closure
|
||||
envs stay unfreeable (documented). Probe-observed the accounting under a tracking GPA (deinit drives
|
||||
live allocs 7→3 in a spawn+sleep+2×go run; residual = envs). Locked by
|
||||
`1820-concurrency-fiber-scheduler-deinit.sx` (one run hits timers + kqueue fd + Tasks; `freed by
|
||||
deinit: 5`, `live after deinit: 5` (env residual), `kq open after run: true`→`kq after deinit: -1`,
|
||||
`read: 3 [97 98 99]`), `.build {"target":"macos"}`. Adversarial review: no real UAF/over-free in the
|
||||
supported deinit-after-`run` path; reconciled a doc contradiction (terminal-contract wording); 0154
|
||||
over-store concern probed + cleared (`kq == -1` right after `init`). Suite GREEN **759/0**.
|
||||
- **B1.4c — real fd-readiness blocking via kqueue (macOS).** De-risked first with a no-scheduler probe
|
||||
(confirmed `size_of(Kevent)==32` and the pipe→kevent roundtrip: `kq_wait` returned 1, `out.ident ==
|
||||
read_fd`, `out.filter == -1`, `out.data == 1` — the struct layout reads the fd back correctly). Then
|
||||
|
||||
Reference in New Issue
Block a user