@@ -29,6 +29,7 @@ emojis. For an even higher-level introduction, see [these][wasmio-2024]
2929 * [ Async Import ABI] ( #async-import-abi )
3030 * [ Async Export ABI] ( #async-export-abi )
3131* [ Examples] ( #examples )
32+ * [ Component Instance Lifetime] ( #component-instance-lifetime )
3233* [ TODO] ( #todo )
3334
3435
@@ -1261,6 +1262,105 @@ return values from `waitable-set.wait` in the previous example. The precise mean
12611262these values is defined by the Canonical ABI.
12621263
12631264
1265+ ## Component Instance Lifetime
1266+
1267+ In settings like [ service workers] and [ serverless computing] , a single
1268+ component instance may handle multiple independent host events by having its
1269+ exported functions called repeatedly (and, if they're ` async ` , concurrently). In
1270+ settings like these, the number and lifetime of component instances and the
1271+ degree of reuse is determined by host policies based on factors like
1272+ utilization, available parallelism and security. However, when component
1273+ instance lifetimes are flexible in this manner and don't have an obvious end
1274+ (as opposed to a traditional CLI setting, where a component instance's lifetime
1275+ conventionally ends right after ` main() ` returns), the host still needs to
1276+ understand the expectations of component authors to enable portability.
1277+
1278+ Before the addition of native concurrency support in Preview 3, a natural
1279+ expectation is that, in the absence of atypical scenarios like timeouts or quota
1280+ exhaustion, a component author can expect that their component instance will not
1281+ be abruptly terminated during the execution of contained Core WebAssembly code.
1282+ But other than that, component authors must conservatively assume that their
1283+ component instance will be torn down at any time.
1284+
1285+ With the addition of native concurrency support, these expectations must be
1286+ nuanced to account for asynchronous Core WebAssembly execution. In particular,
1287+ when using the stackless ` async callback ` ABI, an active task may have no active
1288+ Core WebAssembly function invocation while it is ` WAIT ` ing on a Waitable Set.
1289+ Analogously, when using the stackful ` async ` ABI, a Core WebAssembly function
1290+ invocation will be suspended (as-if by the [ stack-switching] proposal's
1291+ ` suspend ` instruction) when it calls ` waitable-set.wait ` so that there is also
1292+ no Core WebAssembly code actively executing (only a continuation stored in the
1293+ component instance's table of threads).
1294+
1295+ Now, before a task has [ returned] ( #returning ) its value, there is still a
1296+ natural expectation that, even if there is * currently* no active Core
1297+ WebAssembly execution, the task is still * logically* executing and thus the
1298+ component instance will not be terminated (under normal circumstances; timeout
1299+ or quota exhaustion could still abruptly terminate the instance). Furthermore,
1300+ even if a component instance has no active tasks that haven't returned a value,
1301+ if a component instance is holding the readable or writable end of a stream or
1302+ future that the host holds the other end of, there is also a natural expectation
1303+ that the host will keep the component instance alive until all the futures and
1304+ streams have reached a closed state.
1305+
1306+ As an example, even after ` wasi:http/handler ` 's ` handle ` function * returns* a
1307+ ` response ` resource, the ` handle ` function's component instance is expected to
1308+ be kept alive as long as it's holding the un-closed writable end of the
1309+ ` stream<u8> ` contained by the returned ` response ` . If, on the other hand, the
1310+ ` stream<u8> ` was forwarded from the return value of a host import, and so the
1311+ component instance is not holding any writable end, this expectation doesn't
1312+ apply and so all other conditions being met, the component instance can be
1313+ eagerly torn down.
1314+
1315+ The interesting question is what happens once all tasks have returned their
1316+ values * and* all incoming and outgoing streams and futures have been closed if
1317+ the component instance still contains live [ threads] ( #threads-and-tasks ) that
1318+ are currently suspended (and so not actively executing Core WebAssembly code)
1319+ but may potentially be resumed in the future to do important post-return work
1320+ (like performing logging, billing or metrics operations that have been taken off
1321+ the pre-return critical path for peformance reasons).
1322+
1323+ If the component instance is conservatively kept alive (until a hard timeout),
1324+ this may end up wasting resources for periodic background activities that run in
1325+ an infinite (waiting) loop. In particular, cooperative threads used to implement
1326+ pthreads are expected to sometimes be used in this manner. On the other hand,
1327+ immediately tearing down a component instance as soon as the last byte of an
1328+ outgoing stream is written and active Core WebAssembly execution returns or
1329+ suspends will break the abovementioned post-return use cases if they involve
1330+ waiting on ` async ` operations to complete.
1331+
1332+ To resolve this tension, threads are implicitly distinguished by a "keep-alive"
1333+ flag that determines whether the expectation is that the existence of the thread
1334+ is intended to keep the containing component instance alive. In the initial
1335+ release of Preview 3, this "keep-alive" flag is default * set* for the [ implicit
1336+ thread] ( #summary ) created for a task and default * cleared* for the explicit
1337+ threads created by ` thread.new-indirect ` . In particular, this means that an
1338+ ` async callback ` -lifted function will keep its containing component instance
1339+ alive until it returns the ` EXIT ` code (` 0 ` ).
1340+
1341+ As an example, in JavaScript, the Service Worker API's [ ` waitUntil ` ] method
1342+ would delay returning the ` EXIT ` code. In the initial 0.3.0 release without
1343+ cooperative threads (🧵), [ ` setInterval ` ] would also unfortunately delay
1344+ returning the ` EXIT ` code and thus, without guest code intervention, would keep
1345+ component instances alive until timeout limits were hit. The release of
1346+ cooperative threads would offer a solution to this problem, but an awkward one.
1347+ Instead, the [ intention] ( #TODO ) is to add new built-in functions that would
1348+ provide guest code more direct, dynamic control over its own keep-alive
1349+ flags, thereby allowing the JS event loop to clear its keep-alive flag once all
1350+ ` waitUntil ` promises resolved, thereby allowing ` setInterval ` callbacks to keep
1351+ running (while the host wants to keep the instance warm), but still indicating
1352+ to the host that destruction is welcome at any time.
1353+
1354+ Lastly, the above discussion refers to component * instances* , however the host
1355+ cannot tear down independent component instances when they are linked together
1356+ (as this would leave dangling function imports). In general, components must be
1357+ instantiated and destroyed as * trees* , where the host can only choose when to
1358+ instantiate or destroy the * root* component of the tree, and all other child
1359+ instances are instantiated/destroyed along with the root. Thus, when the above
1360+ rules set an expectation that any component instance in a tree be kept alive,
1361+ the whole tree would be kept alive.
1362+
1363+
12641364## TODO
12651365
12661366Native async support is being proposed incrementally. The following features
@@ -1272,6 +1372,8 @@ comes after:
12721372* zero-copy forwarding/splicing
12731373* allow the ` stream<char> ` type to validate; make it use ` string-encoding `
12741374 and not split code points
1375+ * add built-ins providing guest code more control over its containing
1376+ [ component instance's lifetime] ( #component-instance-lifetime )
12751377* some way to say "no more elements are coming for a while"
12761378* add an ` async ` effect on ` component ` type definitions allowing a component
12771379 type to block during instantiation
@@ -1312,11 +1414,16 @@ comes after:
13121414[ Overlapped I/O ] : https://en.wikipedia.org/wiki/Overlapped_I/O
13131415[ `io_uring` ] : https://en.wikipedia.org/wiki/Io_uring
13141416[ `epoll` ] : https://en.wikipedia.org/wiki/Epoll
1417+ [ Serverless Computing ] : https://en.wikipedia.org/wiki/Serverless_computing
13151418
13161419[ `select` ] : https://pubs.opengroup.org/onlinepubs/007908799/xsh/select.html
13171420[ `O_NONBLOCK` ] : https://pubs.opengroup.org/onlinepubs/7908799/xsh/open.html
13181421[ `pthread_create` ] : https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_create.html
13191422
1423+ [ Service Workers ] : https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
1424+ [ `waitUntil` ] : https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil
1425+ [ `setInterval` ] : https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval
1426+
13201427[ AST Explainer ] : Explainer.md
13211428[ Canonical Built-in ] : Explainer.md#canonical-built-ins
13221429[ `context.get` ] : Explainer.md#-contextget
0 commit comments