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

@@ -73,6 +73,12 @@ impl Io for CBlockingIo {
arm_timer :: (self: *CBlockingIo, deadline_ms: i64, park: ParkToken) -> *void {
return null;
}
// No fibers in the blocking model — there is no current execution context to
// register as a fan-in waiter. `race`'s futures are born `.ready` here, so it
// finds a winner without ever parking; this null token is never consulted.
current_park :: (self: *CBlockingIo) -> ParkToken {
return .{ handle = null };
}
}
// --- Future($R): the handle to an async task's eventual result ---