feat: race over Futures via context.io.race (PLAN-IO-UNIFY Phase 4)

Re-home the proven first-wins race from sched.race(*Task) onto *Future handles
+ the Io protocol; the old Task-based race is REPLACED (ufcs overload-by-receiver
is rejected, and only 1821 used it).

- Protocol: add Io.current_park() -> ParkToken — the running fiber as a token,
  captured WITHOUT parking — so race can register the SAME coordinator across N
  futures' park slots, then park once via suspend_raw; any completion readies it.
  Scheduler returns {self.current} (bails outside a fiber); CBlockingIo returns
  {null} (race never parks there — futures are born .ready).
- race :: ufcs (io: Io, futures: $T) -> RaceResult(T), kept in sched.sx (it needs
  meta.sx's make_enum/make_variant; pulling that into the io.sx prelude part-file
  would cycle). Winner scan -> register/park/deregister -> make_variant the winner
  -> Phase-3 cancel each still-.pending loser (no join). RaceResult reused
  unchanged (*Future(R) projects field 0 'value' -> R).
- TRUE-cancel: parked losers stop at their next suspend (timers evicted by cancel's
  wake), so race returns at WINNER-time, not slowest-loser-time.
- Adversarial review fixes: (1) an all-failing/all-cancelling racer set no longer
  deadlock-aborts the scheduler — race bails loudly ('all futures settled without
  a winner') when nothing is .ready and nothing is still .pending; (2) only
  .pending losers are cancelled, so a loser that already .failed keeps its real
  outcome label instead of being stomped to .canceled.

Re-point 1821 to context.io.async + context.io.race (winner a=111, losers
.canceled, completion log only 'task 1 @ 10ms', final clock 10ms — was 30 under
the old cooperative join). New 1826 locks the failing-loser case. Byte-identical
on aarch64-macOS + aarch64-linux. Suite 853/0; .ir churn is the current_park
vtable method.
This commit is contained in:
agra
2026-06-28 09:50:10 +03:00
parent 8bacb2b01c
commit 97b0abef66
51 changed files with 58153 additions and 57125 deletions

View File

@@ -36,6 +36,30 @@ installed via `push Context { io = xx scheduler } { … s.run(); }` — exactly
just with the scheduler now reachable as `context.io`.
## Status (2026-06-28)
- **Phase 4 — `race` over Futures via `context.io.race`. DONE.** Re-homed the
proven first-wins race from `sched.race(*Task)` onto `*Future` handles + the
`Io` protocol; the old Task-based `race` is REPLACED (ufcs overload-by-receiver
is rejected — "duplicate top-level decl" — and only 1821 used it).
- **Protocol affordance:** added `Io.current_park() -> ParkToken` (the running
fiber as a token, captured WITHOUT parking) so race can register the SAME
coordinator across N futures' `park` slots, then park once via `suspend_raw`;
any completion `ready`s it. Scheduler returns `{self.current}` (bails outside
a fiber); CBlockingIo returns `{null}` (race never parks there — futures born
`.ready`). The await comment already anticipated this fan-in.
- **race** (`ufcs (io: Io, futures: $T) -> RaceResult(T)`, in sched.sx — it
needs meta.sx's `make_enum`/`make_variant`, and pulling that into the io.sx
prelude part-file would cycle): winner scan → register+park → deregister →
`make_variant` the winner → Phase-3 `cancel` each loser (NO join). `RaceResult`
reused unchanged (`*Future(R)` projects field 0 `value` → R).
- **Winner-time return:** with true cancellation the parked losers stop at their
next suspend (their timers evicted by cancel's wake), so race returns at the
winner's virtual time, not the slowest loser's. 1821 re-pointed to
`context.io.async` + `context.io.race`: `winner a=111`, losers `.canceled`,
completion log ONLY `task 1 @ 10ms`, final clock `10ms` (was 30 under the old
cooperative join). Byte-identical on aarch64-macOS + aarch64-linux. Suite
853/0; `.ir` churn (current_park vtable method) regenerated, only 1821 stdout
changed otherwise.
- **Phase 3 — TRUE cancellation via `suspend_raw -> !`. DONE.** A cancelled async
worker now abandons its body at its next suspend instead of running to
completion. Pieces: