Issue #987: async (ADR-async-4, async-2): std and runtime - group runtime, handles, par_iter tasks, admission, task::OverBudget

Open and answer this issue on GitHub.

ready · opened 2026-10-30 · updated 2026-11-03 · 1 comment

Part of #983. Records: ADR-async-4 D4-D6, D10, D14; ADR-async-2 D2-D4, D6; ADR-async-3 D3, D7; ADR-async-1 D14, D15; ADR-327 D6 (changed); ADR-346 D4 (changed by async-4 D14). Spec: Part I 8.2; Part II Chapter 12 (memory budget, task::OverBudget, admission), 12.6; Part III 13.3b, 17.1 std::task, the C error code 1102.

Dependency: the memory half of admission and task::OverBudget needs ADR-327 D4-D7 built (#490). Until then admission counts descriptors only; steps 1-4 and 6 do not wait.

To build

  1. The group runtime in crates/nikaia-std/src/task.rs (replacing the overlap<n> vehicles and task::scope): start, hold an outcome, join in written order, cancel, a limit gate; a group of fixed size laid out without an allocation (async-2 D8).
  2. task::ChildHandle[T]: points at the group’s slot, no Send, no heap; join(), join(within:), cancel(), start() for a deferred one. TaskHandle[T] gains start() for async(detached, deferred); spawn stays until #991.
  3. join(within: d) is a limit on this wait (async-4 D14, changes ADR-346 D4, which today’s TaskHandle::join(within:) builds): when d runs out, join throws task::TimedOut, the task keeps running and the handle stays live (joinable again, cancellable, detachable for a TaskHandle). join(within: d, on_timeout: task::OnTimeout::Cancel) cancels the task as cancel() does (cleanups run), throws task::TimedOut, and uses the handle up. Both handles. The runtime keeps a timed-out handle’s slot until it is taken. A task’s own within: deadline that ran out makes a later join throw task::TimedOut again.
  4. par_iter with pausing lambdas: one task per element under admission and limit:, polled by the call; results in input order; every failure kept (first in input order thrown, the rest secondary), a failure slot only where the lambda can throw, allocated on the failure path; limit: replaces workers: on fold/par_fold; within:, admission: on par_iter.
  5. task::Admission, task::Load, task::Decision (shape of ADR-326 D2’s net::Admission); std’s default strategy; a task starts only where a descriptor is free (and, after #490, where its block fits); otherwise waits. The descriptor ceiling: at it a task waits, never fails. (Part II’s Admission block is refused by rustc today because task::Load does not exist; recorded in tests/specification/COMPILES.txt.)
  6. After #490: a block per task (ADR-327 D5); a full block unwinds at the allocation, cleanups run, no panic hook, arrives as task::OverBudget and runs the fallback; in a group it fails only its task; a par_iter lambda that never pauses shares its piece’s block, reset per element only where it allocates and keeps memory (async-2 D6).
  7. task::OverBudget in the C error codes (1102).
  8. Tests in tests/language/src/ (.nika only): a child handle cancelled while sleeping returns at once and the group still ends; a deferred handle runs only after start(); a join(within:) that times out, then a second join() that gets the value; on_timeout: task::OnTimeout::Cancel stops the task (its cleanup runs); admission with a tiny descriptor ceiling waits instead of failing; a pausing par_iter().map keeps input order and every failure; after #490, a task over its budget fails with task::OverBudget and its sibling completes.

task::cancel is not built (removed by ADR-async-4 D10). The names are final (async-4 D13).


Generated by Claude Code

Comments

keywan-ghadami, 2026-11-03

This issue is now built against ADR-async-5 (D1–D4, D9, D11–D14, D20 and D21 for steps 1–2). By #1214 (the specification first), the runtime work in crates/nikaia-std/src/task.rs is done in Rust. The brief for steps 1–2 is docs/briefs/async/a3-async-group-runtime.md. The remaining steps get briefs once a1–a3 have landed.


Generated by Claude Code

Back to Open Issues