PlanPlan — the join graph (docs/internal/CONCURRENCY_ARCH.md §13.5).
A Plan is a LAZY spec tree: constructors build it, nothing runs until await. Leaf methods name WHERE the code runs; composites name shape:
Plan.task:{ b } in-VM task (same heap, ordinary closure) Plan.thread:{ b } thread isolate, portable block Plan.thread:'unit.qn' thread isolate, unit form Plan.process:'unit.qn' child qn process (data-only lanes) Plan.all:#( plans ) structural gather -> isomorphic result List Plan.any:#( plans ) race -> first success, rest cancelled
Worker leaves take send: (the input message) and label: (stamped into the registry: VM.psTree shows plan ownership). Failure policy is a composite attribute: onError:'cancelRest' (default — first error cancels the rest and re-raises) or 'collect' (every slot resolves to #{'ok': v} or #{'err': message}).
Cancellation is per-backing: process leaves are TERMINATED (killed), thread leaves are orphaned (marked orphaned: in the registry — they run to completion detached), task leaves get cooperative cancel. Timeouts compose: Async.timeout:ms do:{ plan.await }.
Structural gather: a composite that launches every plan in plans in parallel; await answers a List isomorphic to the input. The failure policy defaults to 'cancelRest' (see all:onError:).
(Plan.all:#( (Plan.task:{ 1 + 1 }) (Plan.thread:{ 2 + 2 }) )).await "* -> #(2 4)
A gather with an explicit failure policy: 'cancelRest' (the first error cancels the remaining children and re-raises) or 'collect' (no slot is lost — each resolves to #{'ok': v} or #{'err': message}). Any other policy string throws.
(Plan.all:#( (Plan.task:{ 1 }) (Plan.task:{ 'boom'.throw }) ) onError:'collect').await "* -> #(#{'ok': 1} #{'err': 'boom'})
Race: launch every child; the first SUCCESS wins — await answers its value and cancels the rest. Errors don't win the race: if every branch fails, await throws with the last error.
(Plan.any:#( (Plan.task:{ Async.sleep:50; 1 }) (Plan.task:{ 2 }) )).await "* -> 2
Cancel whatever part of a live subtree has not completed: process -> terminate, thread -> orphan + label, task -> cancel.
A child-process leaf: run unit file u in its own qn process. Unit-form only — blocks cannot cross a process boundary — and the lanes carry data-only values.
A process leaf stamped with registry label l (see thread:label:).
A process leaf whose child is sent m as its first message at launch (see thread:send:).
A process leaf with both an input message and a registry label.
Internal: settle a live node from a fresh task (no captured instance state).
An in-VM task leaf: run block b as a Task on this VM's scheduler — same heap, ordinary closure, so it may capture freely (the only leaf kind that can).
(Plan.task:{ 1 + 1 }).await "* -> 2
A thread-isolate leaf: c is a portable block, or a unit path (String) loaded fresh in the isolate. The work runs on its own thread with its own heap; the result deep-copies back.
(Plan.thread:{ 6 * 7 }).await "* -> 42
A thread leaf stamped with registry label l, so VM.psTree shows which plan owns the worker.
A thread leaf whose worker is sent m as its first message at launch — the code reads it with Worker.receive.
(Plan.thread:{ Worker.receive * 2 } send:21).await "* -> 42
A thread leaf with both an input message (see thread:send:) and a registry label (see thread:label:).
Internal: validate a process-leaf target — unit paths (Strings) only, because blocks cannot cross a process boundary (§13.2).
Run the plan: launch the whole spec tree without waiting (that is what makes siblings start in parallel), then join it in structure. Answers the leaf's value or the composite's result; failures re-raise here, subject to the composite's policy. The calling task parks until the answer is ready — compose a deadline with Async.timeout:do:.
(Plan.task:{ 1 + 1 }).await "* -> 2
A fresh Plan defaults to an empty in-VM task leaf under the 'cancelRest' policy; the class-side constructors overwrite from here.
The node's kind: 'task', 'thread' or 'process' for leaves, 'all' or 'any' for composites.
(Plan.task:{ 1 }).kind "* -> task
The registry label for a worker leaf, or nil when unlabelled.
(Plan.thread:{ 1 } label:'demo').label "* -> demo
Phase 1: spawn the whole tree without waiting on anything. A live node is #{'plan' 'handle' 'children' 'done?'} — 'handle' is a Worker or Task handle, nil for composites.
The failure policy a composite settles under: 'cancelRest' (the default) or 'collect'.
Internal setter used by the constructors: stamp a composite kind ('all' / 'any') and its child plans. Answers self for chaining.
Internal setter used by the constructors: stamp the leaf kind and its code (portable block or unit path). Answers self for chaining.
Internal setter used by the constructors: record the registry label stamped onto a worker leaf at launch. Answers self for chaining.
Internal setter used by the constructors: record the message a worker leaf is sent at launch. Answers self for chaining.
Internal setter used by the constructors: set a composite's failure policy ('cancelRest' or 'collect'). Answers self for chaining.
Phase 2: join in structure. Composites join their children through an outcome channel so policy reacts in COMPLETION order, not index order — a fast failure cancels a slow sibling immediately.
Internal join for an 'all' composite: children settle through a shared channel in COMPLETION order — under 'cancelRest' a fast failure cancels the in-flight siblings immediately; under 'collect' each slot files #{'ok': v} or #{'err': message} — while results land by index, so the answer matches the children's order.
Internal join for an 'any' composite: the first success cancels every other child and becomes the answer; when all branches fail, throws with the last error.