@@ -65,6 +65,9 @@ specified here.
6565 * [ ` canon thread.yield-then-resume ` ] ( #-canon-threadyield-then-resume ) 🧵
6666 * [ ` canon thread.suspend-then-promote ` ] ( #-canon-threadsuspend-then-promote ) 🧵
6767 * [ ` canon thread.yield-then-promote ` ] ( #-canon-threadyield-then-promote ) 🧵
68+ * [ ` canon thread.get-task ` ] ( #-canon-threadget-task ) 🧵
69+ * [ ` canon thread.set-task ` ] ( #-canon-threadset-task ) 🧵
70+ * [ ` canon task.drop ` ] ( #-canon-taskdrop ) 🧵
6871 * [ ` canon error-context.new ` ] ( #-canon-error-contextnew ) 📝
6972 * [ ` canon error-context.debug-message ` ] ( #-canon-error-contextdebug-message ) 📝
7073 * [ ` canon error-context.drop ` ] ( #-canon-error-contextdrop ) 📝
@@ -119,7 +122,7 @@ into the `handles` or `threads` fields of `ComponentInstance`.
119122``` python
120123class ComponentInstance :
121124 store: Store
122- handles: Table[ResourceHandle | Waitable | WaitableSet | ErrorContext]
125+ handles: Table[ResourceHandle | Waitable | WaitableSet | ErrorContext | Task ]
123126 threads: Table[Thread]
124127 may_leave: bool
125128 backpressure: int
@@ -609,8 +612,7 @@ class Task:
609612 on_resolve: OnResolve
610613 state: State
611614 num_borrows: int
612- implicit_thread: Optional[Thread]
613- threads: list[Thread]
615+ waiting_to_enter: Optional[Thread]
614616
615617 def __init__ (self , ft , opts , inst , on_start , on_resolve ):
616618 self .ft = ft
@@ -620,22 +622,12 @@ class Task:
620622 self .on_resolve = on_resolve
621623 self .state = Task.State.INITIAL
622624 self .num_borrows = 0
623- self .implicit_thread = None
624- self .threads = []
625+ self .waiting_to_enter = None
625626```
626627
627628The ` Task.needs_exclusive ` predicate returns whether this task's implicit thread
628- (` Task.implicit_thread ` ) has * not* opted in to multiple concurrent linear memory
629- shadow stacks (via "stackful" lift) and thus, according to [ Component Invariant]
630- #2 , requires serialization with all the other implicit threads in the component
631- instance that have similarly not opted in. This question only applies to
632- ` async ` -typed functions, since synchronous functions can't block and thus can
633- always execute in a LIFO fashion using a single linear memory shadow stack. When
634- ` needs_exclusive ` is true, core wasm execution is gated on acquiring the
635- ` ComponentInstance.exclusive_thread ` lock. Due to cooperativity, the
636- ` exclusive_thread ` "lock" is simply a mutable field holding either ` None ` , when
637- unlocked, or, when locked, a reference to the ` Task.implicit_thread ` currently
638- holding the lock.
629+ requires the ` ComponentInstance.exclusive_thread ` lock to acquired and released
630+ in order to enforce [ Component Invariant] #2 .
639631``` python
640632 def needs_exclusive (self ):
641633 assert (self .ft.async_)
@@ -668,29 +660,24 @@ exports.
668660``` python
669661 def enter_implicit_thread (self ):
670662 assert (self .state == Task.State.INITIAL )
671- self .implicit_thread = current_thread()
672663 if self .ft.async_:
673664 def has_backpressure ():
674665 return (self .inst.backpressure > 0 or
675666 (self .needs_exclusive() and self .inst.exclusive_thread is not None ))
676667 if has_backpressure() or self .inst.num_waiting_to_enter > 0 :
668+ self .waiting_to_enter = current_thread()
677669 self .inst.num_waiting_to_enter += 1
678- self .implicit_thread .wait_until(lambda : not has_backpressure())
670+ current_thread() .wait_until(lambda : not has_backpressure())
679671 self .inst.num_waiting_to_enter -= 1
672+ self .waiting_to_enter = None
680673 if self .deliver_pending_cancel():
681674 self .cancel()
682675 return False
683676 if self .needs_exclusive():
684677 assert (self .inst.exclusive_thread is None )
685- self .inst.exclusive_thread = self .implicit_thread
686- self .register_thread( self .implicit_thread )
678+ self .inst.exclusive_thread = current_thread()
679+ current_thread().index = self .inst.threads.add(current_thread() )
687680 return True
688-
689- def register_thread (self , thread ):
690- assert (thread not in self .threads and thread.task is self )
691- self .threads.append(thread)
692- assert (thread.index is None )
693- thread.index = self .inst.threads.add(thread)
694681```
695682Since the order in which suspended threads are resumed is nondeterministic (see
696683` Store.tick ` below), once ` Task.enter_implicit_thread ` suspends the task's
@@ -704,58 +691,44 @@ above definition ensures the following properties:
704691 backpressure (i.e., disabling backpressure never unleashes an unstoppable
705692 thundering herd of pending tasks).
706693
707- Once a task's implicit thread has cleared the backpressure gate, it is added to
708- the lists of threads running inside the current task and component instance by
709- ` Task.register_thread() ` (which is also called by ` thread.new-indirect ` , below).
694+ As shown above, only once a task has cleared the backpressure gate is its
695+ implicit thread visibly added to the component-instance-wide ` threads ` table.
710696
711697Symmetrically, the ` Task.exit_implicit_thread ` method is called before a task's
712698implicit thread returns to reverse the effects of ` Task.enter_implicit_thread ` .
713- In particular, if the ` exclusive_thread ` lock was acquired, it is released.
714- ` Task.unregister_thread ` (which is also called by ` thread.new-indirect ` , below)
715- traps if the task's last thread is unregistered and the task has not yet
716- returned a value to its caller.
717699``` python
718700 def exit_implicit_thread (self ):
719- assert (current_thread() is self .implicit_thread)
720- self .unregister_thread(self .implicit_thread)
701+ self .inst.threads.remove(current_thread().index)
721702 if self .ft.async_ and self .needs_exclusive():
722- assert (self .inst.exclusive_thread is self .implicit_thread )
703+ assert (self .inst.exclusive_thread is current_thread() )
723704 self .inst.exclusive_thread = None
724-
725- def unregister_thread (self , thread ):
726- assert (thread in self .threads and thread.task is self )
727- self .threads.remove(thread)
728- if len (self .threads) == 0 :
729- trap_if(self .state != Task.State.RESOLVED )
730- assert (self .num_borrows == 0 )
731- assert (thread.index is not None )
732- self .inst.threads.remove(thread.index)
733705```
734706
735707The ` Task.request_cancellation ` method implements the ` OnCancel ` callback that
736708is called by the ` subtask.cancel ` built-in. If a task's implicit thread is
737709waiting to start (in ` Task.enter_implicit_thread ` , defined above) due to
738710backpressure, then it is immediately cancelled without running any guest code.
739- Otherwise, if the task's implicit thread is ` cancellable ` and ` ready ` , it is
740- resumed, allowing it to execute until returning or blocking. Currently, only
741- ` callback ` threads that have returned to their event loop are ` cancellable ` .
742- When resumed, the ` callback ` thread will call ` Task.deliver_pending_cancel ` ,
743- which will return ` True ` , leading to the ` TASK_CANCELLED ` event code being
744- passed to the ` callback ` function. Ideally, the ` callback ` function will
745- promptly call ` task.cancel ` (or ` task.return ` ) to resolve the task before the
746- ` OnCancel ` callback returns, allowing ` subtask.cancel ` to return without
747- blocking.
711+ Otherwise, if the task contains one or more threads that is ` cancellable ` and
712+ ` ready ` , one nondeterministically picked and resumed to allow it to execute
713+ until returning or blocking. Currently, only ` callback ` threads that have
714+ returned to their event loop are ` cancellable ` . When resumed, the ` callback `
715+ thread will call ` Task.deliver_pending_cancel ` , which will return ` True ` ,
716+ leading to the ` TASK_CANCELLED ` event code being passed to the ` callback `
717+ function. Ideally, the ` callback ` function will promptly call ` task.cancel ` (or
718+ ` task.return ` ) to resolve the task before the ` OnCancel ` callback returns,
719+ allowing ` subtask.cancel ` to return without blocking.
748720``` python
749721 def request_cancellation (self ):
750722 if self .state == Task.State.INITIAL :
751723 self .state = Task.State.PENDING_CANCEL
752- self .implicit_thread .resume()
724+ self .waiting_to_enter .resume()
753725 assert (self .state == Task.State.RESOLVED )
754726 else :
755727 assert (self .state == Task.State.STARTED )
756728 self .state = Task.State.PENDING_CANCEL
757- if self .implicit_thread.cancellable and self .implicit_thread.ready():
758- self .implicit_thread.resume()
729+ candidates = { t for t in self .inst.threads if t.task is self and t.cancellable and t.ready() }
730+ if candidates:
731+ random.choice(list (candidates)).resume()
759732
760733 def has_pending_cancel (self ):
761734 return self .state == Task.State.PENDING_CANCEL
@@ -3374,10 +3347,12 @@ are lifted according to `lift_flat_values` above, the optional `post-return`
33743347function (specified as a ` canonopt ` immediate of ` canon lift ` ) is called,
33753348passing the same core wasm results as parameters so that the ` post-return `
33763349function can free any associated allocations.
3350+ TODO: copy from set-task-old (and also alll the other stuff in Concurrency.md)
33773351``` python
33783352 if not opts.async_:
33793353 flat_results = call_and_trap_on_throw(callee, flat_args)
33803354 assert (types_match_values(flat_ft.results, flat_results))
3355+ trap_if(thread.task is not task)
33813356 result = lift_flat_values(cx, MAX_FLAT_RESULTS , CoreValueIter(flat_results), ft.result_type())
33823357 task.return_(result)
33833358 if opts.post_return is not None :
@@ -3394,11 +3369,11 @@ functions can always be implemented by a plain synchronous function call
33943369without the need for fibers which would otherwise be necessary if the
33953370` post-return ` function performed a blocking operation.
33963371
3397- In both of the ` async ` cases below (with or without ` callback ` ), the
3398- ` task.return ` built-in must be called, providing the return value as core wasm
3399- * parameters * to the ` task.return ` built-in (rather than as core function
3400- results as in the synchronous case). If ` task.return ` is * not * called by the
3401- time the ` Task ` 's last ` Thread ` exits, there is a trap (in ` Task.unregister_thread ` ) .
3372+ In both of the ` async ` cases below (with or without ` callback ` ), the return
3373+ value is provided by calling the ` task.return ` built-in, passing the return
3374+ value as core wasm * parameters * (rather than as core function results as in
3375+ the synchronous case). If ` task.return ` never ends up being called, the
3376+ task will never complete for the caller .
34023377
34033378In the ` async ` non-` callback ` ("stackful async") case, there is a single call
34043379to the core wasm callee which must return empty core results. Waiting for async
@@ -3426,7 +3401,7 @@ function (specified as a `funcidx` immediate in `canon lift`) until the
34263401 if thread.task.deliver_pending_cancel():
34273402 event = (EventCode.TASK_CANCELLED , 0 , 0 )
34283403 else :
3429- assert (inst.exclusive_thread is task.implicit_thread )
3404+ assert (inst.exclusive_thread is thread )
34303405 inst.exclusive_thread = None
34313406 thread.cancellable = True
34323407 match code:
@@ -3444,7 +3419,7 @@ function (specified as a `funcidx` immediate in `canon lift`) until the
34443419 trap()
34453420 assert (inst.exclusive_thread is None )
34463421 thread.cancellable = False
3447- inst.exclusive_thread = task.implicit_thread
3422+ inst.exclusive_thread = thread
34483423 event_code, p1, p2 = event
34493424 [packed] = call_and_trap_on_throw(opts.callback, [event_code, p1, p2])
34503425 code,si = unpack_callback_result(packed)
@@ -4490,17 +4465,17 @@ class CoreFuncRef:
44904465 callee: Callable[[list[CoreValType]], list[CoreValType]]
44914466
44924467def canon_thread_new_indirect (ft , ftbl : Table[CoreFuncRef], fi , c ):
4493- task = current_task ()
4494- trap_if(not task. inst.may_leave)
4468+ inst = current_instance ()
4469+ trap_if(not inst.may_leave)
44954470 f = ftbl.get(fi)
44964471 assert (ft == CoreFuncType([' i32' ], []) or ft == CoreFuncType([' i64' ], []))
44974472 trap_if(f.t != ft)
44984473 def thread_func ():
44994474 [] = call_and_trap_on_throw(f.callee, [c])
4500- task.unregister_thread (new_thread)
4501- new_thread = Thread(task , thread_func)
4475+ inst.threads.remove (new_thread.index )
4476+ new_thread = Thread(current_task() , thread_func)
45024477 assert (new_thread.suspended())
4503- task.register_thread (new_thread)
4478+ new_thread.index = inst.threads.add (new_thread)
45044479 return [new_thread.index]
45054480```
45064481The newly-created thread starts out in a "suspended" state and so, to
@@ -4679,6 +4654,66 @@ def canon_thread_yield_then_promote(i):
46794654```
46804655
46814656
4657+ ###### 🧵 ` canon thread.get-task `
4658+
4659+ For a canonical definition:
4660+ ``` wat
4661+ (canon thread.get-task (core func $thread.get-task))
4662+ ```
4663+ validation specifies:
4664+ * ` $thread.get-task ` is given type ` (func (result i32)) `
4665+
4666+ Calling ` $thread.get-task ` invokes the following function which TODO
4667+ ``` python
4668+ def canon_thread_get_task ():
4669+ thread = current_thread()
4670+ trap_if(not thread.task.inst.may_leave)
4671+ taski = thread.task.inst.handles.add(thread.task)
4672+ return [taski]
4673+ ```
4674+
4675+
4676+ ###### 🧵 ` canon thread.set-task `
4677+
4678+ For a canonical definition:
4679+ ``` wat
4680+ (canon thread.set-task (core func $thread.set-task))
4681+ ```
4682+ validation specifies:
4683+ * ` $thread.set-task ` is given type ` (func (param i32)) `
4684+
4685+ Calling ` $thread.set-task ` invokes the following function which TODO
4686+ ``` python
4687+ def canon_thread_set_task (taski ):
4688+ thread = current_thread()
4689+ trap_if(not thread.task.inst.may_leave)
4690+ new_task = thread.task.inst.handles.get(taski)
4691+ trap_if(not isinstance (new_task, Task))
4692+ thread.task = new_task
4693+ return []
4694+ ```
4695+
4696+
4697+ ###### 🧵 ` canon task.drop `
4698+
4699+ For a canonical definition:
4700+ ``` wat
4701+ (canon thread.drop (core func $task.drop))
4702+ ```
4703+ validation specifies:
4704+ * ` $thread.drop ` is given type ` (func (param i32)) `
4705+
4706+ Calling ` $task.drop ` invokes the following function which TODO
4707+ ``` python
4708+ def canon_task_drop (taski ):
4709+ inst = current_instance()
4710+ trap_if(not inst.may_leave)
4711+ task = inst.handles.remove(taski)
4712+ trap_if(not isinstance (task, Task))
4713+ return []
4714+ ```
4715+
4716+
46824717### 📝 ` canon error-context.new `
46834718
46844719For a canonical definition:
0 commit comments