Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions design/mvp/Binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,26 +327,24 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
| 0x1d opts:<opts> => (canon error-context.debug-message opts (core func)) 📝
| 0x1e => (canon error-context.drop (core func)) 📝
| 0x1f => (canon waitable-set.new (core func)) 🔀
| 0x20 cancel?:<cancel?> m:<core:memoryidx> => (canon waitable-set.wait cancel? (memory m) (core func)) 🔀
| 0x21 cancel?:<cancel?> m:<core:memoryidx> => (canon waitable-set.poll cancel? (memory m) (core func)) 🔀
| 0x20 0x00 m:<core:memoryidx> => (canon waitable-set.wait (memory m) (core func)) 🔀
| 0x21 0x00 m:<core:memoryidx> => (canon waitable-set.poll (memory m) (core func)) 🔀
| 0x22 => (canon waitable-set.drop (core func)) 🔀
| 0x23 => (canon waitable.join (core func)) 🔀
| 0x26 => (canon thread.index (core func)) 🧵
| 0x27 ft:<core:typeidx> tbl:<core:tableidx> => (canon thread.new-indirect ft tbl (core func)) 🧵
| 0x28 => (canon thread.resume-later (core func)) 🧵
| 0x29 cancel?:<cancel?> => (canon thread.suspend cancel? (core func)) 🧵
| 0x0c cancel?:<cancel?> => (canon thread.yield cancel? (core func)) 🔀
| 0x2a cancel?:<cancel?> => (canon thread.suspend-then-resume cancel? (core func)) 🧵
| 0x2b cancel?:<cancel?> => (canon thread.yield-then-resume cancel? (core func)) 🧵
| 0x2c cancel?:<cancel?> => (canon thread.suspend-then-promote cancel? (core func)) 🧵
| 0x2d cancel?:<cancel?> => (canon thread.yield-then-promote cancel? (core func)) 🧵
| 0x29 0x00 => (canon thread.suspend (core func)) 🧵
| 0x0c 0x00 => (canon thread.yield (core func)) 🔀
| 0x2a 0x00 => (canon thread.suspend-then-resume (core func)) 🧵
| 0x2b 0x00 => (canon thread.yield-then-resume (core func)) 🧵
| 0x2c 0x00 => (canon thread.suspend-then-promote (core func)) 🧵
| 0x2d 0x00 => (canon thread.yield-then-promote (core func)) 🧵
| 0x40 shared?:<sh?> ft:<core:typeidx> => (canon thread.spawn-ref shared? ft (core func)) 🧵②
| 0x41 shared?:<sh?> ft:<core:typeidx> tbl:<core:tableidx> => (canon thread.spawn-indirect shared? ft tbl (core func)) 🧵②
| 0x42 shared?:<sh?> => (canon thread.available-parallelism shared? (core func)) 🧵②
async? ::= 0x00 =>
| 0x01 => async
cancel? ::= 0x00 =>
| 0x01 => cancellable
sh? ::= 0x00 =>
| 0x01 => shared 🧵②
opts ::= opt*:vec(<canonopt>) => opt*
Expand Down Expand Up @@ -539,6 +537,8 @@ named once.
* The two `list` type codes should be merged into one with an optional immediate
and similarly for `func`.
* The redundant `0x00` and `0x01` opcodes of `nameattributes` will be merged.
* The vestigial `0x00` in `thread.*` and `waitable-set.*` built-ins may be
removed.
* Most built-ins should have a `<canonopt>*` immediate instead of an ad hoc
subset of `canonopt`s.
* Add optional `shared` immediate to all canonical definitions (explicitly or
Expand Down
672 changes: 292 additions & 380 deletions design/mvp/CanonicalABI.md

Large diffs are not rendered by default.

68 changes: 34 additions & 34 deletions design/mvp/Concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,37 +790,37 @@ cancelled-before-started and cancelled-before-returned. A subtask in one of
these terminal states is said to be **resolved**. A resolved subtask has always
dropped all the borrowed handles that it was lent during the call.

Cancellation is *cooperative*, delivering the request for cancellation to one
of the subtask's threads and then allowing the subtask to continue executing
for an arbitrary amount of time (calling imports, performing I/O and everything
else) until the subtask decides to call [`task.cancel`] to confirm the
cancellation or, for whatever reason, call `task.return` as-if there had been
no cancellation. `task.cancel` enforces the same "all borrowed handles dropped"
rule as `task.return`, so that once a subtask is resolved, the caller knows its
lent handles have been returned. If the subtask was waiting to start due to
backpressure, the subtask is immediately aborted without running the callee at
all.

When `subtask.cancel` is called, it will attempt to immediately resume one of
the subtask's threads which is in a cancellable state, passing it a sentinel
"cancelled" value. A thread is in a "cancellable" state if it calls one of the
[blocking](#blocking) built-ins with the `cancellable` immediate set (indicating
that the caller expects and propagates cancellation appropriately) or, if using
a `callback`, returns to the event loop and no other thread is holding the
"exclusive" lock. If a subtask has no cancellable threads, no thread is resumed
and the request for cancellation is remembered in the task state, to be
delivered at the next cancellable opportunity. In the worst case, though, a
component may *never* wait cancellably and thus cancellation may be silently
ignored.
Cancellation is *cooperative*, delivering a *request* for cancellation and then
allowing the subtask to continue executing for an arbitrary amount of time
(calling imports, performing I/O and everything else) until the subtask decides
to call [`task.cancel`] to confirm the cancellation or, for whatever reason,
call `task.return` as-if there had been no cancellation. `task.cancel` enforces
the same "all borrowed handles dropped" rule as `task.return`, so that once a
subtask is resolved, the caller knows its lent handles have been returned. If
the subtask was waiting to start due to backpressure, the subtask is immediately
aborted without running the callee at all.

When `subtask.cancel` is called, it first records a "pending cancellation
request" on the callee's task. If the callee's task was waiting to start due to
[backpressure](#backpressure), `subtask.cancel` returns "cancelled before
started" immediately. Otherwise, `subtask.cancel` performs a nonblocking,
cooperative `thread.yield` that allows the host to nondeterministically schedule
any cooperative threads it wants in the hopes of delivering the cancellation
request to the callee. Currently, cancellation requests can only be delivered
to `async` functions using the stackless `callback` ABI. In this case, a pending
cancellation request is delivered as a "task cancelled" event to the `callback`
function, following the standard run-to-completion rules mentioned above. In
the [future](#TODO), other cancellation delivery mechanisms will be added for
the `async` functions using the stackful ABI.

`subtask.cancel` can be called synchronously or asynchronously. If called
synchronously, `subtask.cancel` blocks until the subtask reaches a resolved
state and returns which state was reached. If called asynchronously, then if a
cancellable subtask thread is resumed *and* the subtask reaches a resolved
state before blocking for whatever reason `subtask.cancel` will return
which state was reached. Otherwise, `subtask.cancel` will return a "blocked"
sentinel value and the caller must [wait](#waitables-and-waitable-sets) via
waitable set until the subtask reaches a resolved state.
synchronously, `subtask.cancel` will block until the subtask has resolved (i.e.,
the subtask returns a value or calls `task.cancel`), returning whether the
callee "returned" or "cancelled before returning". If called *asynchronously*
and the callee has *still* not resolved after the host decides the cooperative
yield has completed, `subtask.cancel` will return "blocked" instead and the
caller must [wait](#waitables-and-waitable-sets) via waitable set until the
subtask reaches a resolved state.

The Component Model does not provide a mechanism to force prompt termination of
threads as this can lead to leaks and corrupt state in a still-live component
Expand Down Expand Up @@ -869,16 +869,14 @@ defined by the Component Model:
* If multiple tasks are blocked by backpressure and the backpressure is
disabled, the order in which these pending tasks start, along with how
they interleave with new tasks, is nondeterministic.
* If a task containing multiple threads is cancelled, the choice of which
thread receives the request for cancellation is nondeterministic.
* When `subtask.cancel` is called for a task that has started (i.e., passed the
backpressure gate), the host may nondeterministically resume as many or as few
threads as it wants before `subtask.cancel` returns.

Despite the above, the following scenarios do behave deterministically:
* If a component `a` asynchronously calls the export of another component `b`,
control flow deterministically transfers to `b` and then back to `a` when
`b` returns or blocks.
* If a component `a` asynchronously cancels a subtask in another component `b`,
control flow deterministically transfers to `b` and then back to `a` when `b`
resolves or blocks.
* If a component `a` asynchronously cancels a subtask in another component `b`
that was blocked before starting due to backpressure, cancellation completes
deterministically and immediately.
Expand Down Expand Up @@ -1481,6 +1479,8 @@ the concurrency story:
* remove the temporary trap mentioned above that occurs when a `read` and
`write` of a stream/future happen from within the same component instance
* zero-copy forwarding/splicing
* allow `async` functions using the stackful ABI to be notified of
cancellation
* allow the `stream<char>` type to validate; make it use `string-encoding`
and not split code points
* add built-ins providing guest code more control over its containing
Expand Down
Loading
Loading