From ed9a79c18e8687fab3dbc104e4a1c1ba3e77af1a Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 20 Jan 2025 16:27:34 -0600 Subject: [PATCH] Define how start+async work --- design/mvp/Async.md | 58 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/design/mvp/Async.md b/design/mvp/Async.md index 7189cc29..70ce51de 100644 --- a/design/mvp/Async.md +++ b/design/mvp/Async.md @@ -544,6 +544,47 @@ return values from `task.wait` in the previous example. The precise meaning of these values is defined by the Canonical ABI. +## Interaction with the start function + +Since any component-level function with an empty signature can be used as a +[`start`] function, there's nothing to stop an `async`-lifted function from +being a `start` function. Async start functions are useful in scenarios like +the following: +* If the top-level scripts of a scripting language are executed by the `start` + function, asychrony arises from regular use of the language's concurrency + features. For example, in JS, this takes the form of [top-level `await`]. +* If C++ or other OOPLs global object constructors are executed by the `start` + function, these can execute general-purpose code which may need to perform + concurrent I/O. + +Since component `start` definitions are already specified to make synchronous +function calls that must return before the next `start` definition executes or +and before the component instance is considered initialized (and ready for +export calls), the natural extension to `async`-lifted functions is for `start` +to wait until the `async` callee [returns](#returning), just like a synchronous +`canon lower`. This gives `async` `start` functions a simple way to do +concurrent work and signal completion using the same language bindings as +regular `async` `export` functions. + +As explained above, an async task can always continue executing after reaching +the "returned" state and thus an `async` task spawned by a `start` definition +may continue executing even after it has returned and the component instance is +considered fully initialized. These post-return tasks can be used by the +language toolchain to implement traditional "background tasks" (e.g., the +`setInterval()` or `requestIdleCallback()` JavaScript APIs). From the +perspective of [structured concurrency], these background tasks are new task +tree roots, sibling to the roots created when component exports are called by +the host. Thus, subtasks (and, later, threads) spawned by the background task +will have proper async callstacks as used to define reentrancy and for +debugging/profiling. + +In future, when [runtime instantiation] is added to the Component Model, the +component-level function used to create a component instance could be lowered +with `async` to allow a parent component instance to do instantiation of child +components concurrently with other tasks, relaxing the fully synchronous +model of instantiation presented above. + + ## Interaction with multi-threading For now, the integration between multi-threading (via [`thread.spawn`]) and @@ -580,16 +621,6 @@ it does present interesting opportunities to experiment with optimizations over time as applications are built with more components. -## Interaction with the start function - -Since component-level start functions can be any component-level function (with -type `[] -> []`), async functions can be start functions. This raises some -interesting questions concerning how much concurrency during instantiation (of -a whole component DAG) is allowed and how parent components can control this. -For now, this remains a [TODO](#todo) and validation will reject `async`-lifted -`start` functions. - - ## TODO Native async support is being proposed incrementally. The following features @@ -598,8 +629,6 @@ will be added in future chunks roughly in the order list to complete the full comes after: * `nonblocking` function type attribute: allow a function to declare in its type that it will not transitively do anything blocking -* define what `async` means for `start` functions (top-level await + background - tasks), along with cross-task coordination built-ins * `subtask.cancel`: allow a supertask to signal to a subtask that its result is no longer wanted and to please wrap it up promptly * zero-copy forwarding/splicing @@ -653,10 +682,13 @@ comes after: [Use Cases]: ../high-level/UseCases.md [Blast Zone]: FutureFeatures.md#blast-zones [Reentrance]: Explainer.md#component-invariants +[`start`]: Explainer.md#start-definitions [stack-switching]: https://github.com/WebAssembly/stack-switching/ [JSPI]: https://github.com/WebAssembly/js-promise-integration/ [shared-everything-threads]: https://github.com/webAssembly/shared-everything-threads - [WASI Preview 3]: https://github.com/WebAssembly/WASI/tree/main/wasip2#looking-forward-to-preview-3 [`wasi:http/handler.handle`]: https://github.com/WebAssembly/wasi-http/blob/main/wit-0.3.0-draft/handler.wit +[Runtime Instantiation]: https://github.com/WebAssembly/component-model/issues/423 + +[Top-level `await`]: https://github.com/tc39/proposal-top-level-await