Skip to content
Merged
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
41 changes: 41 additions & 0 deletions bundled/componentize_py_async_support/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""This module contains code to integrate Component Model async features with
Python's `asyncio` framework.

Most of the code here is used internally by code generated by `componentize-py`
for worlds which use async features. As of this writing, the only item meant to
be used directly by application code is the `spawn` function.

"""
import asyncio
import componentize_py_runtime
import subprocess
Expand Down Expand Up @@ -441,6 +449,11 @@ async def _return_result(export_index: int, borrows: int, coroutine: Any) -> Non
_future_state.get().pending_count -= 1

def first_poll(export_index: int, borrows: int, coroutine: Any) -> int:
"""Internal function called by generated code for exported functions.

This is not meant to be called by application code.

"""
context = Context()
future_state = _FutureState(None, {}, [], 1)
context.run(_set_future_state, future_state)
Expand All @@ -464,6 +477,11 @@ def _poll(future_state: _FutureState) -> int:
return _CallbackCode.WAIT | (waitable_set << 4)

def callback(event0: int, event1: int, event2: int) -> int:
"""Internal function called by generated code for exported functions.

This is not meant to be called by application code.

"""
future_state = componentize_py_runtime.context_get()
componentize_py_runtime.context_set(None)

Expand Down Expand Up @@ -493,6 +511,11 @@ def callback(event0: int, event1: int, event2: int) -> int:
return _poll(future_state)

async def await_result[T](result: Result[T, tuple[int, int]]) -> T:
"""Internal function called by generated code for imported functions.

This is not meant to be called directly by application code.

"""
global _loop
global _future_state

Expand Down Expand Up @@ -522,6 +545,24 @@ async def _wrap_spawned(coroutine: Any) -> None:
_future_state.get().pending_count -= 1

def spawn(coroutine: Any) -> None:
"""Spawn an awaitable to be executed as part of the current Component Model
task.

Each call to an async function exported by a component creates a new
Component Model task which is represented as a Python awaitable. By
default, that task will exit after returning a value. However, this
function may be used to do additional work concurrently, and that additional
work may extend the lifetime of the task arbitrarily after it has returned a
value.

For example, implementing `wasi:http/service@0.3.x` involves returning a
`response` resource, but sending the response body must happen after
returning since it involves writing to a stream which the caller will only
have received as part of the `response` resource. Therefore, the only way
to write the response body is by using this function. See the `http-p3`
example in the `componentize-py` Git repository for what that looks like.

"""
global _future_state

_future_state.get().pending_count += 1
Expand Down
64 changes: 60 additions & 4 deletions bundled/componentize_py_async_support/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,40 @@
T = TypeVar('T')

class FutureReader(Generic[T]):
"""Represents the readable end of a Component Model `future`.

Each object of this type should be closed promptly using either context
management (e.g. a `with` statement) or by calling `read` in order to notify
the owner of the writable end of the `future` that the readable end has been
closed. If the object becomes unreachable without being closed, it will be
closed via finalization.

"""
def __init__(self, type_: int, handle: int):
"""Constructor for internal use by generated code.

Application code should not call this directly. Instead,
`componentize-py` will generate a constructor function for each unique
`future` type used by the target world. Each such function will return
a (`FutureReader`, `FutureWriter`) pair and use this constructor behind
the scenes.

"""
self.type_ = type_
self.handle: int | None = handle
self.finalizer = weakref.finalize(self, componentize_py_runtime.future_drop_readable, type_, handle)

async def read(self) -> T:
"""Asynchronously read the value sent to this `future`.

The awaitable returned by this function will resolve when a value has
been delivered to the `future`.

Calling this function consumes the target object; any attempt to call
`read` more than once will raise an `AssertionError`.

"""

self.finalizer.detach()
handle = self.handle
self.handle = None
Expand Down Expand Up @@ -42,23 +70,51 @@ def __exit__(self,

return None

async def write(type_: int, handle: int, value: Any) -> None:
async def _write(type_: int, handle: int, value: Any) -> None:
await componentize_py_async_support.await_result(
componentize_py_runtime.future_write(type_, handle, value)
)
componentize_py_runtime.future_drop_writable(type_, handle)

def write_default(type_: int, handle: int, default: Callable[[], Any]) -> None:
componentize_py_async_support.spawn(write(type_, handle, default()))
def _write_default(type_: int, handle: int, default: Callable[[], Any]) -> None:
componentize_py_async_support.spawn(_write(type_, handle, default()))

class FutureWriter(Generic[T]):
"""Represents the writable end of a Component Model `future`.

Each object of this type should be closed promptly using either context
management (e.g. a `with` statement) or by calling `write` in order to send
a value to the readable end of the `future. If the object becomes
unreachable without being closed, it will be closed via finalization after
sending the default value which was specified during construction.

"""
def __init__(self, type_: int, handle: int, default: Callable[[], T]):
"""Constructor for internal use by generated code.

Application code should not call this directly. Instead,
`componentize-py` will generate a constructor function for each unique
`future` type used by the target world. Each such function will return
a (`FutureReader`, `FutureWriter`) pair and use this constructor behind
the scenes.

"""
self.type_ = type_
self.handle: int | None = handle
self.default = default
self.finalizer = weakref.finalize(self, write_default, type_, handle, default)
self.finalizer = weakref.finalize(self, _write_default, type_, handle, default)

async def write(self, value: T) -> bool:
"""Asynchronously write a value to the `future`.

The awaitable returned by this function will resolve once either the
value has been delivered to the readable end of the `future` or the
readable end has been closed.

Calling this function consumes the target object; any attempt to call
`write` more than once will raise an `AssertionError`.

"""
self.finalizer.detach()
handle = self.handle
self.handle = None
Expand Down
Loading
Loading