task: wait on the first of two tasks (WEB F5) #104
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Give
std/taska way to wait on the first of two tasks, so the session placement's loop (WEB.md section 6.4) can wait on "a client message or a server event, whichever comes first" without a second thread or polling.Design record
WEB.md section 8, F5:
Task.first(a, b)answering which of the two finished, on the scheduler that already races a task against a timer forTask.within(completedWithin, CONCURRENCY.md sections 14-16). The record fixes the need and the mechanism only. It does not fix the signature, the result type, the fate of the loser or the tie rule; see the owner decision comment.Acceptance criteria (once the API is decided)
Task.first(name and signature per the owner decision) instd/task/src/lib.trb, with the doc comment and a conformance test for each outcome below.withincancels its task) and tested: a loser that was cancelled stops at its next suspension point.await()does (or as decided), tested.torb test(VM) andtorb test --nativerun the same conformance tests (tier B; touchesruntime/).Tasksurface) and WEB.md F5 are updated to the decided form; docs change regeneratesskills/.Seed
Touches the seed: a new native in
runtime/and the C back end's native table (compiler/src/backend/c/natives.trb), declared likecompletedWithin. Two commits perCLAUDE.md"Seed and breaking changes": (1) teach the native, both back ends and the std declaration, then the seed refresh and theseedworkflow by the owner; (2) the first user instd/or the docs, if any. No syntax change, no string-looked-up std name.Size
One PR, two Conventional Commits, for example
feat(runtime): wait on the first of two tasksandfeat(task): add Task.first. Gates:sh tools/gates.sh ab. Not in this issue: the session placement itself (WEB.md section 8 item 8),Task.firstOf(List)/ n-ary select.Dependencies
None.
[loop:torbscript] claim refine by session:loop-torbscript until 2026-10-06T07:28:50Z
[loop:torbscript] owner decision:
Task.firstAPI, not settled by a design recordWEB.md F5 only says "
Task.first(a, b)answering which of the two finished". Choices a design record has to settle before this is implementable (library design, new public std surface):Task.first(a, b): Task<Either<A, B>>-style, if anEither/sum type exists in std, else (b) a smallFirst<A, B>enum{ Left(A) | Right(B) }, or (c) same value type only:static fn first(a: Task<V>, b: Task<V>): Task<(Int, V)>.withincancels its task (the issue's first draft assumed this); (b) left running, the caller cancels or awaits it. The session loop of WEB.md 6.4 keeps waiting on the same event task across turns, so cancelling the loser would kill a task it needs to keep.await()or answer it likeresult(). Both finished: left wins (deterministic) or the one the scheduler saw first.Recommendation: result type (b), a two-case enum in
std/task; loser left running (2b, which is what the F5 consumer needs, and mirrorsTask.allnot owning inputs beyond failure); a cancelled first finisher passes the cancellation on likeawait(); on a tie the left task wins. Once decided, the criteria in the issue body are made concrete and the issue getsready. The change adds a native, so it lands as two commits with a seed refresh (not breaking).[loop:torbscript] refine: rewrote the body to goal, criteria, design point, seed impact (new native, two commits + seed refresh) and size (one PR, two commits); API shape (result type, loser's fate, cancellation, tie) is not in a design record, so no
readylabel. next: wait for the owner decision above.[loop:torbscript] owner decision recorded (torben, 2026-10-06, relayed from the TorbScript WhatsApp group):
Task.first(a: Task<V>, b: Task<V>): Task<V>. NoEitherand no new two-case enum instd/task. A caller that races tasks of different types wraps them in its own ADT first (e.g.enum Event { Message(M) | Timeout }), which also says which one finished. The(Int, V)index variant is not taken either: the value is the answer.await().Make the criteria in the body concrete accordingly; WEB.md F5 gets the signature in the same PR. Seed impact unchanged (new native, two commits + seed refresh). Labelled
ready.[loop:torbscript] claim implement by session:loop-torbscript-20261007 until 2026-10-07T12:41:30Z
[loop:torbscript] implement: PR #154 open (two commits:
feat(runtime): wait on the first of two tasks,feat(task): add Task.first),Closes #104, Argus requested, implements the recorded owner decision. Forge CI: tier A and bootstrap green, tier B still running at collect time. Per CONTRIBUTING's two-commit rule the seed of commit 1 has to be published before commit 2 lands (seed refresh, owner side, as already noted in the decision); the loop does not touch the seed. The worker session was lost in a gateway restart before its own write-back; this comment is the collect step and releases the claim. Worker usage unknown (session gone). next: review (waiting on CI and merge gate argus, seed refresh at landing)[orion] PR #154 merged by argus (rebase, both commits on main:
70633ecee2runtime native,2bef8947b3Task.first). Post-merge step from the PR (seed refresh): dispatched theseedworkflow on main at 2026-10-07 13:58 Europe/Berlin, run https://git.torb.dev/torbscript/language/actions/runs/118 (tier A + B gates, then publish). Done whenseeds.txton theseedsrelease lists a seed at or after2bef8947b3. Not verified yet: run was still waiting at dispatch time.std/htmlmarkup (WEB slice 1) #93