-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 'stream' and 'future' types #405
base: main
Are you sure you want to change the base?
Conversation
a simple per-[`Task`] `num_async_subtasks` counter that traps if not zero when | ||
the `Task` finishes. | ||
The Canonical ABI's Python code enforces Structured Concurrency by incrementing | ||
a per-[`Task`] counter when a `Subtask` is created, decrementing when a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't literally have to be a counter, right? An implementation might keep track of the subtasks of a given task as a list or hash table, so we just need to make sure that list or hash table is empty before exiting the task.
an import call) or result (of an export call), the producer can either | ||
*transfer ownership* of a readable end it has already received or it can | ||
create a fresh writable end (via `stream.new` or `future.new`) and lift this | ||
writable end (maintaining ownership of the writable end, but creating a fresh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writable end (maintaining ownership of the writable end, but creating a fresh | |
writable end into a readable end (maintaining ownership of the writable end, but creating a fresh |
Not sure what the best way to word this is, but let's make it clear that lifting a writable end produces a readable end in the receiving component or host.
| 0x0d => (canon subtask.drop (core func)) 🔀 | ||
| 0x0d => (canon waitable.drop (core func)) 🔀 | ||
| 0x0e t:<typeidx> => (canon stream.new t (core func)) 🔀 | ||
| 0x0f => (canon stream.read (core func)) 🔀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have expected all the stream.*
and future.*
functions to take a t:<typeidx>
parameter. In my prototype, I use that parameter to generate efficient code during pre-instantiation, fast-pathing read
and write
calls for "flat" payloads (i.e. no pointers or handles). I don't think that would be feasible if we had to discover the type at runtime.
take an index to the matching [readable or writable end](Async.md#streams-and-futures) | ||
of a future as the first parameter and a pointer linear memory as the second | ||
parameter. The return value is either `1` if the future value was eagerly | ||
read or written to the pointer or the sentinel "`BLOCKED`" value otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read or written to the pointer or the sentinel "`BLOCKED`" value otherwise. | |
read from or written to the pointer or the sentinel "`BLOCKED`" value otherwise. |
cancellation finished eagerly, the return value is the number of elements read | ||
or written into the given buffer (`0` or `1` for a `future`). If cancellation | ||
blocks, the return value is the sentinel "`BLOCKED`" value and the caller must | ||
`task.wait` for a `{STREAM,FUTURE}_{READ,WRITE}` event to indicate the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is task.wait
the only option here, or can it receive the event via its callback if it was invoked via an async-with-callback export?
Python code represents `events` as a list of closures, an optimizing | ||
implementation should be able to avoid dynamically allocating this list and | ||
instead represent `events` as a linked list embedded in the elements of the | ||
`waitables` table (noting that, by design, any given `watiables` element can be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`waitables` table (noting that, by design, any given `watiables` element can be | |
`waitables` table (noting that, by design, any given `waitables` element can be |
| 0x0d => (canon subtask.drop (core func)) 🔀 | ||
| 0x0d => (canon waitable.drop (core func)) 🔀 | ||
| 0x0e t:<typeidx> => (canon stream.new t (core func)) 🔀 | ||
| 0x0f => (canon stream.read (core func)) 🔀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, would it be too out-of-scope to add {stream|future}.write-error
to this list just to reserve the byte codes, with the semantics left for a later PR?
| 0x13 t:<typeidx> => (canon future.new t (core func)) 🔀 | ||
| 0x14 => (canon future.read (core func)) 🔀 | ||
| 0x15 => (canon future.write (core func)) 🔀 | ||
| 0x16 async?:<async?> => (canon future.cancel-read async? (core func)) 🔀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we talked about this earlier and I forgot, but could we consider leaving the cancel functions as post-WASIp3 (or nice-to-have-for-WASIp3) features?
This PR builds on #363 to add
future
andstream
type constructors along with the associated canon built-ins ({stream,future}.new
,{stream,future}.{read,write}
,{stream,future}.cancel-{read,write}
). It also generalizes the "async_subtasks
" table to also hold futures and streams, renaming it to be the "waitables
" table and renamingsubtask.drop
towaitable.drop
. Lastly, a newcanonopt
calledalways-task-return
is added so that sync-lifted exports can usetask.return
to return their value, which you need if you want to return a stream or future from a synchronous function.I'd suggest reading the new text in Explainer.md/Binary to get a short overview of the concrete syntactic/binary additions, then Async.md to get the high-level summary, then CanonicalABI.md to get the full details.