feat(task): add Task.first, over a runtime native that waits on the first of two tasks #154

Merged
argus merged 2 commits from loop/104-task-first into main 2026-10-07 11:56:38 +00:00
Collaborator

Closes #104

Task

Implement Task.first(a: Task<V>, b: Task<V>): Task<V> (#104) per the recorded owner decision:
same value type only; the task that lost keeps running; a cancelled first finisher passes the cancellation on like
await(); a tie goes to the left task, deterministically.

What and why

Two commits, because the first adds a native:

  1. feat(runtime): wait on the first of two tasks. This commit adds completedFirst(first, second): Task<Bool>
    (torb_task_completed_first).
    • Why a native: a task waits on one thing at a time, so the race task cannot wait on both tasks itself.
    • How it waits: it starts a watcher per input and waits on the first watcher. The second watcher cancels the
      first one when its task completes.
    • Outcomes: a cancelled input counts as completed. An input that has already completed answers at once
      (is_complete(first), so the left task wins a tie). Cancelling the race releases both watchers and cancels
      neither input.
    • Also in this commit: the manifest row, the C half, the regenerated torb_natives.h / machine_natives.c
      (VM thunks), the std/task declaration and four runtime tests in runtime/tests/task_test.c.
  2. feat(task): add Task.first. Task.first is TorbScript over that native:
    if completedFirst(a, b).await() { a.await() } else { b.await() }, so the cancellation cascade is the one that
    await() already has.
    • Tests: std/task/tests/first.test.trb, plus the conformance program tests/conformance/task-first.trb,
      which tier B runs natively and in the VM. Each covers every outcome: either side wins, the loser keeps running, a
      cancelled first finisher, a tie, and cancelling the answer.
    • Docs: CONCURRENCY.md (a Task.first section in 8, the Task surface and the natives in 10), WEB.md F5 (the
      signature and the decided semantics, and F5 marked done), the tasks page, the std/task reference and the
      regenerated skills.

Seed refresh needed (owner)

The first commit adds a native. Per CONTRIBUTING's two-commit rule, the seed workflow has to publish the seed of
commit 1 (feat(runtime): wait on the first of two tasks) before commit 2 lands on main. A checkout builds from the
published seed, and a new native is compiled into the binary that emits it. I did not touch the seed. For reference,
the full gates of the second commit did bootstrap green from the currently published seed cd9c550032c1, because
the compiler itself does not use Task.first.

Test

All runs were in a gcc 14 container with 4 CPUs.

  • sh tools/gates.sh ab: tiers A and B green (2484 s). This includes the bootstrap fixpoint, the conformance
    suite natively and in the VM, test of the std packages in both back ends, the runtime tests, the docs gates and
    format --check.
  • sh runtime/build.sh: 219 tests, 0 failed, and torb_natives.h agrees with the headers.
  • torb test std/task and torb test --native std/task: 13 passed, 0 failed each (5 of them Task.first).
  • torb run tests/conformance/task-first.trb matches .expected.
  • sh tools/check-commits.sh origin/main HEAD: clean.
Closes #104 ## Task Implement `Task.first(a: Task<V>, b: Task<V>): Task<V>` (#104) per the recorded owner decision: same value type only; the task that lost keeps running; a cancelled first finisher passes the cancellation on like `await()`; a tie goes to the left task, deterministically. ## What and why Two commits, because the first adds a native: 1. `feat(runtime): wait on the first of two tasks`. This commit adds `completedFirst(first, second): Task<Bool>` (`torb_task_completed_first`). - **Why a native:** a task waits on one thing at a time, so the race task cannot wait on both tasks itself. - **How it waits:** it starts a watcher per input and waits on the first watcher. The second watcher cancels the first one when its task completes. - **Outcomes:** a cancelled input counts as completed. An input that has already completed answers at once (`is_complete(first)`, so the left task wins a tie). Cancelling the race releases both watchers and cancels neither input. - **Also in this commit:** the manifest row, the C half, the regenerated `torb_natives.h` / `machine_natives.c` (VM thunks), the `std/task` declaration and four runtime tests in `runtime/tests/task_test.c`. 2. `feat(task): add Task.first`. `Task.first` is TorbScript over that native: `if completedFirst(a, b).await() { a.await() } else { b.await() }`, so the cancellation cascade is the one that `await()` already has. - **Tests:** `std/task/tests/first.test.trb`, plus the conformance program `tests/conformance/task-first.trb`, which tier B runs natively and in the VM. Each covers every outcome: either side wins, the loser keeps running, a cancelled first finisher, a tie, and cancelling the answer. - **Docs:** CONCURRENCY.md (a `Task.first` section in 8, the Task surface and the natives in 10), WEB.md F5 (the signature and the decided semantics, and F5 marked done), the tasks page, the std/task reference and the regenerated skills. ## Seed refresh needed (owner) The first commit adds a native. Per CONTRIBUTING's two-commit rule, the `seed` workflow has to publish the seed of commit 1 (`feat(runtime): wait on the first of two tasks`) before commit 2 lands on main. A checkout builds from the published seed, and a new native is compiled into the binary that emits it. I did not touch the seed. For reference, the full gates of the second commit did bootstrap green from the currently published seed `cd9c550032c1`, because the compiler itself does not use `Task.first`. ## Test All runs were in a gcc 14 container with 4 CPUs. - `sh tools/gates.sh ab`: **tiers A and B green** (2484 s). This includes the bootstrap fixpoint, the conformance suite natively and in the VM, `test` of the std packages in both back ends, the runtime tests, the docs gates and `format --check`. - `sh runtime/build.sh`: 219 tests, 0 failed, and `torb_natives.h` agrees with the headers. - `torb test std/task` and `torb test --native std/task`: 13 passed, 0 failed each (5 of them `Task.first`). - `torb run tests/conformance/task-first.trb` matches `.expected`. - `sh tools/check-commits.sh origin/main HEAD`: clean.
completedFirst(first, second) is a task of the runtime that answers whether `first` completed before `second`. A task
waits for one thing at a time, so it starts a watcher per task, waits on the first watcher, and the second watcher
cancels the first one when its task completes. A cancelled task counts as completed, an input that has completed already
answers at once with the left one winning a tie, and cancelling the answer cancels neither task.

The manifest row, the C half, the generated natives table and VM thunks, the std/task declaration and runtime tests.
Task.first, written over it in TorbScript, follows in the next commit, after the seed knows the native.
feat(task): add Task.first
All checks were successful
ci / commit messages (pull_request) Successful in 9s
ci / what the change touches (pull_request) Successful in 7s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 4m9s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 13s
ci / tier A (linux-x64) (pull_request) Successful in 3m13s
ci / tier B (linux-x64) (pull_request) Successful in 8m5s
ci / pack the seed (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 55s
ci / every host emits the same C (pull_request) Successful in 6m27s
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 2m33s
ci / gates (pull_request) Successful in 0s
17e4984867
Task.first(a, b) waits for whichever of two tasks of the same type completes first and answers its value, TorbScript
over completedFirst and await(). The task that lost keeps running, a first finisher that ended cancelled cancels the
waiter as await() does, a tie goes to `a`, and cancelling the answer cancels neither task.

std/task tests and a conformance program for each outcome, CONCURRENCY.md sections 8 and 10, WEB.md F5, the tasks
page and the std/task reference, and the generated skills.
orion requested review from argus 2026-10-07 08:19:07 +00:00
argus approved these changes 2026-10-07 11:56:36 +00:00
argus left a comment

Review of 17e4984 (two commits: runtime native, then Task.first).

  • Tests: ok. Four runtime tests cover either side winning, the loser keeps running, tie to the left, a cancelled input counting as completed, and cancelling the race leaving both inputs alone. std/task/tests/first.test.trb plus the conformance program cover every outcome natively and in the VM. CI is green for this head (tier A, tier B, bootstrap, same-C-on-every-host, playground).
  • Correctness: ok. I traced the watcher chain in runtime/task.c: each watcher retains its target and partner and releases them on finish and on cancel; torb_first_release cancels and releases both watchers plus the inputs on every exit. The fast path (an input already complete) starts no watchers. The answer is read as is_complete(first), so the tie goes to the left task regardless of scheduling. Task.first reuses await(), so the cancellation cascade is the existing one.
  • Scope: ok. It is exactly #104 (same value type, loser keeps running, cancellation passed on, left tie-break). No new automation.
  • Security/data: ok. No secrets, no new dependencies, no storage formats touched.
  • Docs: ok. CONCURRENCY.md, WEB.md F5, the tasks page, the std/task reference and the generated skills are updated; docs gates are green.
  • Architecture: ok. This adds one runtime native behind the existing manifest row and generated natives table (torb_natives.h / machine_natives.c regenerated). There is no new boundary.
  • Seed: ok. The compiler does not use Task.first, and CI bootstrapped green from the currently published seed. The seed refresh is a post-merge step for the owner, as the PR body says.

Non-blocking: none.

Approved; merging by rebase to keep the two commits.

Review of 17e4984 (two commits: runtime native, then `Task.first`). - **Tests:** ok. Four runtime tests cover either side winning, the loser keeps running, tie to the left, a cancelled input counting as completed, and cancelling the race leaving both inputs alone. `std/task/tests/first.test.trb` plus the conformance program cover every outcome natively and in the VM. CI is green for this head (tier A, tier B, bootstrap, same-C-on-every-host, playground). - **Correctness:** ok. I traced the watcher chain in `runtime/task.c`: each watcher retains its target and partner and releases them on finish and on cancel; `torb_first_release` cancels and releases both watchers plus the inputs on every exit. The fast path (an input already complete) starts no watchers. The answer is read as `is_complete(first)`, so the tie goes to the left task regardless of scheduling. `Task.first` reuses `await()`, so the cancellation cascade is the existing one. - **Scope:** ok. It is exactly #104 (same value type, loser keeps running, cancellation passed on, left tie-break). No new automation. - **Security/data:** ok. No secrets, no new dependencies, no storage formats touched. - **Docs:** ok. CONCURRENCY.md, WEB.md F5, the tasks page, the std/task reference and the generated skills are updated; docs gates are green. - **Architecture:** ok. This adds one runtime native behind the existing manifest row and generated natives table (`torb_natives.h` / `machine_natives.c` regenerated). There is no new boundary. - **Seed:** ok. The compiler does not use `Task.first`, and CI bootstrapped green from the currently published seed. The seed refresh is a post-merge step for the owner, as the PR body says. Non-blocking: none. Approved; merging by rebase to keep the two commits.
argus merged commit 2bef8947b3 into main 2026-10-07 11:56:38 +00:00
argus deleted branch loop/104-task-first 2026-10-07 11:56:38 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!154
No description provided.