Skip to content

Commit 9979d51

Browse files
committed
Also prohibit blocking during the start function
1 parent 5f68460 commit 9979d51

File tree

2 files changed

+26
-42
lines changed

2 files changed

+26
-42
lines changed

design/mvp/Concurrency.md

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -841,42 +841,22 @@ Despite the above, the following scenarios do behave deterministically:
841841

842842
## Interaction with the start function
843843

844-
Since any component-level function with an empty signature can be used as a
845-
[`start`] function, there's nothing to stop an `async`-lifted function from
846-
being used as a `start` function. Async start functions are useful when
847-
executing general-purpose code at initialization time, e.g.:
848-
* If the top-level scripts of a scripting language are executed by the `start`
849-
function, asychrony arises from regular use of the language's concurrency
850-
features. For example, in JS, this takes the form of [top-level `await`].
851-
* If C++ or other OOPLs global object constructors are executed by the `start`
852-
function, these can execute general-purpose code which may use concurrent
853-
I/O APIs.
854-
855-
Since component `start` functions are already defined to be executed
856-
synchronously before the component is considered initialized and ready for its
857-
exports to be called, the natural thing for `start` to do when calling an
858-
`async`-lifted function is wait for the callee to reach the ["returned"
859-
state](#returning). This gives `async` `start` functions a simple way to do
860-
concurrent initialization and signal completion using the same language
861-
bindings as regular `async` `export` functions.
862-
863-
However, as explained above, an async task can always continue executing after
864-
reaching the "returned" state and thus an async task spawned by `start` may
865-
continue executing even after the component instance is initialized and
866-
receiving export calls. These post-return `start`-tasks can be used by the
867-
language toolchain to implement traditional "background tasks" (e.g., the
868-
`setInterval()` or `requestIdleCallback()` JavaScript APIs). From the
869-
perspective of [structured concurrency], these background tasks are new task
870-
tree roots (siblings to the roots created when component exports are
871-
called by the host). Thus, subtasks and threads spawned by the background task
872-
will have proper async callstacks as used to define reentrancy and support
873-
debugging/profiling/tracing.
874-
875-
In future, when [runtime instantiation] is added to the Component Model, the
876-
component-level function used to create a component instance could be lowered
877-
with `async` to allow a parent component to instantiate child components
878-
concurrently, relaxing the fully synchronous model of instantiation supported
879-
by declarative instantiation and `start` above.
844+
All start functions (both component-level and Core WebAssembly start functions
845+
called via `core instance` definition) implicitly have the component-level
846+
function type `func()`, i.e., they are synchronous and take and return no
847+
arguments. Based on the above description of synchronous functions, this means
848+
that start functions may not block before returning. However, if a
849+
component-level start function is lifted using the async ABI, it *may* block
850+
after calling `task.return`, and may thus serve as a long-running "background
851+
task" to which work can be dispatched (e.g., via the `setInterval()` or
852+
`requestIdleCallback()` JavaScript APIs). From the perspective of [structured
853+
concurrency], these background tasks are new task tree roots (siblings to the
854+
roots created when component exports are called by the host).
855+
856+
As a Preview 3 follow-up [TODO](#TODO), component type definitions should be
857+
extended to allow an `async` effect that declares that component instantiation
858+
is allowed to [block](#blocking). This would be necessary to implement, e.g.,
859+
JS [top-level `await`] or I/O in C++ constructors executing during `start`.
880860

881861

882862
## Async ABI
@@ -1280,6 +1260,10 @@ comes after:
12801260
`write` of a stream/future happen from within the same component instance
12811261
* zero-copy forwarding/splicing
12821262
* some way to say "no more elements are coming for a while"
1263+
* add an `async` effect on `component` type definitions allowing a component
1264+
type to block during instantiation
1265+
* add an `async` effect on `resource` type definitions allowing a resource
1266+
type to block during its destructor
12831267
* `recursive` function type attribute: allow a function to opt in to
12841268
recursive [reentrance], extending the ABI to link the inner and
12851269
outer activations

test/async/sync-barges-in.wast

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
(func $start (global.set $ws (call $waitable-set.new)))
2626
(start $start)
2727

28-
(func (export "blocker") (result i32)
28+
(func (export "blocker")
2929
;; wait on $ws, which is initially empty, but will be populated with
3030
;; a completed future when "unblocker" synchronously barges in.
3131
(local $ret i32)
@@ -35,7 +35,7 @@
3535
(if (i32.ne (i32.const 0 (; COMPLETED ;)) (i32.load (i32.const 4)))
3636
(then unreachable))
3737

38-
global.get $unblock-value
38+
(call $task.return (global.get $unblock-value))
3939
)
4040

4141
(func (export "blocker-cb") (result i32)
@@ -79,9 +79,9 @@
7979
(global.set $unblock-value (local.get $val))
8080
)
8181

82-
(func (export "yielder") (result i32)
82+
(func (export "yielder")
8383
(drop (call $thread.yield))
84-
(global.get $unblock-value)
84+
(call $task.return (global.get $unblock-value))
8585
)
8686
(func (export "yielder-cb") (result i32)
8787
(i32.const 1 (; YIELD ;))
@@ -127,11 +127,11 @@
127127
(export $R' "R" (type $R))
128128
(export $S' "S" (type $S))
129129
(func (export "new-R") (param "rep" u32) (result (own $R')) (canon lift (core func $new-R)))
130-
(func (export "blocker") async (result u32) (canon lift (core func $cm "blocker")))
130+
(func (export "blocker") async (result u32) (canon lift (core func $cm "blocker") async))
131131
(func (export "blocker-cb") async (result u32) (canon lift (core func $cm "blocker-cb") async (callback (func $cm "blocker-cb-cb"))))
132132
(func (export "unblocker") (param "val" u32) (canon lift (core func $cm "unblocker")))
133133
(func (export "new-S") (param "rep" u32) (result (own $S')) (canon lift (core func $new-S)))
134-
(func (export "yielder") async (result u32) (canon lift (core func $cm "yielder")))
134+
(func (export "yielder") async (result u32) (canon lift (core func $cm "yielder") async))
135135
(func (export "yielder-cb") async (result u32) (canon lift (core func $cm "yielder-cb") async (callback (func $cm "yielder-cb-cb"))))
136136
(func (export "poker") (param "val" u32) (canon lift (core func $cm "poker")))
137137
)

0 commit comments

Comments
 (0)