@@ -268,11 +268,14 @@ Thread
268268where a ** component store** is the top-level "thing" and analogous to a Core
269269WebAssembly [ store] .
270270
271- The reason for the thread/task split is that, when one thread creates a new
272- thread, the new thread is contained by the task of the original thread which
273- creates an N:1 relationship between threads and tasks that ties N threads to
274- the original export call (= "task") that transitively spawned those N threads.
275- This relationship serves several purposes described in the following sections.
271+ The reason for the thread/task split is that a single component export call,
272+ which spawns a task, can spawn N more threads as part of the execution of that
273+ task, thereby creating an N:1 relationship between threads and tasks.
274+
275+ While the store: instance and instance: task relationships are immutably set on
276+ creation, the task: thread relationship is * mutable* : guest code running inside a
277+ component instance can change which task a thread is currently executing on
278+ behalf of by calling the [ ` thread.set-task ` ] built-in, as described below.
276279
277280In the Canonical ABI explainer, threads, tasks, component instances and
278281component stores are represented by the [ ` Thread ` ] , [ ` Task ` ] ,
@@ -311,9 +314,15 @@ supertask, they can be thought of as a single node in the async call stack.
311314
312315A subtask/supertask relationship is immutably established when an import is
313316called, setting the [ current task] ( #current-thread-and-task ) as the supertask
314- of the new subtask created for the import call. Thus, one reason for
315- associating every thread with a "containing task" is to ensure that there is
316- always a well-defined async call stack.
317+ of the new subtask created for the import call. Thus, one reason for associating
318+ every thread with a "containing task" is to ensure that there is always a
319+ well-defined async call stack. Note that guest code can call [ ` thread.set-task ` ]
320+ to change the containing task of a thread and thus the async call stack can
321+ change completely between two program points while executing on a single thread.
322+ For example, when a JS runtime flushes its microtask queue and encounters a
323+ JS callback associated with a task other than the current thread's task, the JS
324+ runtime would call ` thread.set-task ` so that the async call stack matches the
325+ JS developer's expectation.
317326
318327The async call stack is not currently observable to running components, except
319328that it may nondeterministically appear as part of the callstack stored in
@@ -336,7 +345,7 @@ not enforcing a stricter form of Structured Concurrency at the Component Model
336345level is that there are important use cases where forcing a supertask's thread
337346to stay resident just to wait for subtasks to finish would waste resources
338347without tangible benefit. Instead, we can say that once a supertask's last
339- thread finishes execution , the supertask semantically "tail calls" any still-
348+ thread exits or leaves , the supertask semantically "tail calls" any still-
340349executing subtasks, staying technically-alive and on the async call stack until
341350they complete, but not consuming real resources.
342351
@@ -374,13 +383,23 @@ New threads are created with the [`thread.new-indirect`] built-in. As mentioned
374383[ above] ( #threads-and-tasks ) , a spawned thread inherits the task of the spawning
375384thread which is why threads and tasks are N:1. ` thread.new-indirect ` adds a new
376385thread to the component instance's threads table and returns the ` i32 ` index of
377- this table entry to the Core WebAssembly caller. Like [ ` pthread_create ` ] ,
378- ` thread.new-indirect ` takes a Core WebAssembly function (via index into a
379- ` funcref ` table) and a "closure" parameter to pass to the function when called
380- on the new thread. However, unlike ` pthread_create ` , the new thread is
381- initially in a "suspended" state and must be explicitly "resumed" using one of
382- the following 3 thread built-ins. Once the thread is resumed, the thread can
383- learn its own index by calling the [ ` thread.index ` ] built-in.
386+ this table entry to the Core WebAssembly caller.
387+
388+ After creation, the implicitly-set containing task of a thread can be explicitly
389+ overridden using the [ ` thread.set-task ` ] built-in. ` thread.set-task ` sets the
390+ containing task of the current thread to the task of some other thread that was
391+ retrieved via [ ` thread.get-task ` ] . ` thread.get-task ` always allocates a fresh
392+ handle storing a reference to the current thread's containing task, returning
393+ the ` i32 ` of this new handle which must be explicitly dropped via [ ` task.drop ` ]
394+ to avoid leaking the task. Tasks are thus (acyclicly) kept alive by any or all
395+ of: contained threads, subtask handles and task handles.
396+
397+ Like [ ` pthread_create ` ] , ` thread.new-indirect ` takes a Core WebAssembly function
398+ (via index into a ` funcref ` table) and a "closure" parameter to pass to the
399+ function when called on the new thread. However, unlike ` pthread_create ` , the
400+ new thread is initially in a "suspended" state and must be explicitly "resumed"
401+ using one of the following 3 thread built-ins. Once the thread is resumed, the
402+ thread can learn its own index by calling the [ ` thread.index ` ] built-in.
384403
385404A suspended thread (identified by thread-table index) can be resumed at some
386405nondeterministic point in future via the [ ` thread.resume-later ` ] built-in. In
@@ -725,10 +744,7 @@ the "started" state.
725744The way an ` async ` export returns its value using the async ABI is by calling
726745[ ` task.return ` ] , passing the core values that are to be lifted as * parameters* .
727746When using the async ABI, * any* of the threads contained by a task can call
728- ` task.return ` ; there is no "main thread" of a task. When the last thread of a
729- task returns, there is a trap if ` task.return ` has not been called. Thus, * some*
730- thread (either the thread created implicitly for the initial export call or some
731- thread transitively created by that thread) must call ` task.return ` .
747+ ` task.return ` ; there is no "main thread" of a task.
732748
733749Returning values by calling ` task.return ` allows a task to continue executing
734750even after it has passed its initial results to the caller. This is also
@@ -1560,6 +1576,9 @@ the concurrency story:
15601576[ `thread.yield-then-resume` ] : Explainer.md#-threadyield-then-resume
15611577[ `thread.suspend-then-promote` ] : Explainer.md#-threadsuspend-then-promote
15621578[ `thread.yield-then-promote` ] : Explainer.md#-threadyield-then-promote
1579+ [ `thread.set-task` ] : Explainer.md#-threadset-task
1580+ [ `thread.get-task` ] : Explainer.md#-threadget-task
1581+ [ `task.drop` ] : Explainer.md#-taskdrop
15631582[ `{stream,future}.new` ] : Explainer.md#-streamnew-and-futurenew
15641583[ `{stream,future}.{read,write}` ] : Explainer.md#-streamread-and-streamwrite
15651584[ `stream.cancel-write` ] : Explainer.md#-streamcancel-read-streamcancel-write-futurecancel-read-and-futurecancel-write
0 commit comments