feat: reclaim fiber + async heap (close the closure-env / Future leaks)

Closes the documented per-spawn closure-env leak and most of the async leak,
using only the existing closure.env / closure.fn_ptr field accessors — no compiler
change. Also names the fat-pointer ABI in core.sx (ClosureRaw / SliceRaw) so the
underlying {fn_ptr, env} / {ptr, len} layout is discoverable in one place.

- Fiber body env: Scheduler.reap_fiber frees f.body.env via f.dctx.allocator (the
  spawn-time allocator snapshotted in dctx) at all three reap sites (run/poll/
  deinit). 1820's 'live after deinit' 3 -> 0.
- Async box + closure envs: sx_run_boxed_closure frees the ThunkBox, the
  completion-closure env, and the worker's env (new ThunkBox.worker_env) the
  instant the worker completes.
- Async Future: two-flag ownership — Future.worker_done (set at the end of the
  completion closure) + consumed (set at the end of await); fut_release frees the
  heap Future (via the captured Future.alloc) when BOTH are set, so the LAST of
  {worker, await} reclaims it. await now CONSUMES the future (single-use; touching
  it afterward is a use-after-free — documented). Residual for an AWAITED future
  is 0 (lock: examples/concurrency/1827); a never-awaited future (fire-and-forget /
  race loser) keeps only its Future struct — the structured-concurrency remainder.

Self-reviewed across orderings (await-after/before-complete, cancel-then-await,
cancel-while-parked, double-free via await+deinit, race residual, blocking impl,
cross-allocator reap) — all deterministic, no UAF/double-free. Suite 855/0;
byte-identical on aarch64-macOS + aarch64-linux; .ir churn is the core.sx +
Future/ThunkBox field additions.
This commit is contained in:
agra
2026-06-28 16:19:04 +03:00
parent aae7d72a66
commit 2b1307a0dc
52 changed files with 168342 additions and 160106 deletions

View File

@@ -36,6 +36,31 @@ installed via `push Context { io = xx scheduler } { … s.run(); }` — exactly
just with the scheduler now reachable as `context.io`.
## Status (2026-06-28)
- **Follow-up — heap leak reclamation (fiber-env + async). DONE.** Closed the
documented per-spawn closure-env leak and most of the async leak, using only the
existing `closure.env`/`.fn_ptr` field accessors (now also named by
`ClosureRaw`/`SliceRaw` ABI-view structs in core.sx) — NO compiler change.
- **Fiber body env:** `Scheduler.reap_fiber` frees `f.body.env` via
`f.dctx.allocator` (the spawn-time allocator snapshotted in `dctx`) at all 3
reap sites. 1820's `live after deinit` 3 → **0**.
- **Async box + closure envs:** `sx_run_boxed_closure` frees the `ThunkBox`, the
completion-closure env, and the worker's env (new `ThunkBox.worker_env`) the
instant the worker completes.
- **Async Future:** two-flag ownership — `Future.worker_done` (set at the end of
the completion closure) + `consumed` (set at the end of `await`); `fut_release`
frees the heap `Future` (via the stored `Future.alloc`) when BOTH are set, so
the LAST of {worker, await} reclaims it. `await` now CONSUMES the future
(single-use; documented). Residual for an AWAITED future: **0** (lock:
`examples/concurrency/1827-...`). A NEVER-awaited future (fire-and-forget /
`race` loser) keeps only its `Future` struct (consumed never set) — the
structured-concurrency remainder, deferred.
- Self-reviewed across orderings (await-after/before-complete, cancel-then-await,
cancel-while-parked, double-free via await+deinit, race residual, blocking
impl, cross-allocator reap) — all deterministic, no UAF/double-free. Suite
855/0; byte-identical on aarch64-macOS + aarch64-linux; `.ir` churn (core.sx +
Future/ThunkBox field additions) regenerated, only 1820 stdout changed
otherwise.
- **Phase 5 — CONVERGE: retire the bespoke fiber async API. DONE. Io unification
COMPLETE.** The bespoke `Task` layer (`Task`/`TaskState`/`TaskErr`/`go`/`wait`/
`cancel(Task)` + `Scheduler.task_allocs` and its deinit handling, ~130 lines)