pgrayy opened PR #13341 from pgrayy:upstream/poll-with-notify to bytecodealliance:main:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform, exercises pending→ready wakeup cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy edited PR #13341:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Motivation
We are building Python bindings (wasmtime-py) that call WASM component functions using
asyncio. The component useswasi:httpbacked by wasmtime's internal Tokio runtime. Today,wasmtime_call_future_pollusesWaker::noop(), so the only way to know when to re-poll is busy-polling withasyncio.sleep(0). This wastes CPU and adds latency.With
poll_with_notify, Python creates a socket pair, passes the write fd to wasmtime, and awaits readability on the read end vialoop.sock_recv(). When Tokio completes background I/O and wakes the future, theSocketWakerwrites a byte, waking Python's event loop. Zero CPU while waiting, sub-millisecond wakeup latency.The same embedder also needs to call pure-compute exports (e.g.
greet,add) synchronously for simplicity, while usingcall_asyncfor I/O-bound exports. Since linking WASI async bindings setsasync_requiredon the store, sync calls are rejected by default.Config::async_allow_syncopts the embedder into managing this themselves.Design decisions
Socket-based waker (not a callback): A socket fd integrates naturally with any event loop that supports
poll/epoll/kqueue/IOCP. A function pointer callback would require documenting thread-safety constraints and wouldn't integrate with event loops without additional plumbing.Separate function (not modifying
wasmtime_call_future_poll): C doesn't support optional parameters. Adding a parameter would break all existing callers. A new function is non-breaking and follows the existing_asyncsuffix convention.Config-level
async_allow_sync(not Store-level): This is an embedding strategy decision, not a per-instance decision. Config is immutable after engine creation, matching how other async settings (async_stack_size, etc.) work. The Rust borrow checker already prevents misuse for Rust embedders; this flag is specifically for C API embedders who take responsibility for dispatch.Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform socket helpers, exercises pending→waker fires→ready cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy edited PR #13341:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Motivation
We are building Python bindings (wasmtime-py) that call WASM component functions using
asyncio. The component useswasi:httpbacked by wasmtime's internal Tokio runtime. Today,wasmtime_call_future_pollusesWaker::noop(), so the only way to know when to re-poll is busy-polling withasyncio.sleep(0). This wastes CPU and adds latency.With
poll_with_notify, Python creates a socket pair, passes the write fd to wasmtime, and awaits readability on the read end vialoop.sock_recv(). When Tokio completes background I/O and wakes the future, theSocketWakerwrites a byte, waking Python's event loop.The same embedder also needs to call pure-compute exports (e.g.
greet,add) synchronously for simplicity, while usingcall_asyncfor I/O-bound exports. Since linking WASI async bindings setsasync_requiredon the store, sync calls are rejected by default.Config::async_allow_syncopts the embedder into managing this themselves.Design decisions
Socket-based waker (not a callback): A socket fd integrates naturally with any event loop that supports
poll/epoll/kqueue/IOCP. A function pointer callback would require documenting thread-safety constraints and wouldn't integrate with event loops without additional plumbing.Separate function (not modifying
wasmtime_call_future_poll): C doesn't support optional parameters. Adding a parameter would break all existing callers. A new function is non-breaking and follows the existing_asyncsuffix convention.Config-level
async_allow_sync(not Store-level): This is an embedding strategy decision, not a per-instance decision. Config is immutable after engine creation, matching how other async settings (async_stack_size, etc.) work. The Rust borrow checker already prevents misuse for Rust embedders; this flag is specifically for C API embedders who take responsibility for dispatch.Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform socket helpers, exercises pending→waker fires→ready cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy edited PR #13341:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Motivation
We are building Python bindings (wasmtime-py) that call WASM component functions using
asyncio. The component useswasi:httpbacked by wasmtime's internal Tokio runtime. Today,wasmtime_call_future_pollusesWaker::noop(), so the only way to know when to re-poll is busy-polling withasyncio.sleep(0). This wastes CPU and adds latency.With
poll_with_notify, Python creates a socket pair, passes the write fd to wasmtime, and awaits readability on the read end vialoop.sock_recv(). When Tokio completes background I/O and wakes the future, theSocketWakerwrites a byte, waking Python's event loop.For convenience, the same embedder also wants to call non-suspending exports synchronously (avoiding the overhead of setting up a socket pair and poll loop for functions that will complete immediately). Since linking WASI async bindings sets
async_requiredon the store, sync calls are rejected by default.Config::async_allow_synclets the embedder opt into managing sync/async dispatch themselves.Design decisions
Socket-based waker (not a callback): A socket fd integrates naturally with any event loop that supports
poll/epoll/kqueue/IOCP. A function pointer callback would require documenting thread-safety constraints and wouldn't integrate with event loops without additional plumbing.Separate function (not modifying
wasmtime_call_future_poll): C doesn't support optional parameters. Adding a parameter would break all existing callers. A new function is non-breaking and follows the existing_asyncsuffix convention.Config-level
async_allow_sync(not Store-level): This is an embedding strategy decision, not a per-instance decision. Config is immutable after engine creation, matching how other async settings (async_stack_size, etc.) work. The Rust borrow checker already prevents misuse for Rust embedders; this flag is specifically for C API embedders who take responsibility for dispatch.Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform socket helpers, exercises pending→waker fires→ready cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy edited PR #13341:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Motivation
We are building Python bindings (wasmtime-py) that call WASM component functions using
asyncio. The component useswasi:httpbacked by wasmtime's internal Tokio runtime. Today,wasmtime_call_future_pollusesWaker::noop(), so the only way to know when to re-poll is busy-polling withasyncio.sleep(0). This wastes CPU and adds latency.With
poll_with_notify, Python creates a socket pair, passes the write fd to wasmtime, and awaits readability on the read end vialoop.sock_recv(). When Tokio completes background I/O and wakes the future, theSocketWakerwrites a byte, waking Python's event loop.For convenience, the same embedder also wants to call non-suspending exports synchronously (avoiding the overhead of setting up a socket pair and poll loop for functions that will complete immediately). Since linking WASI async bindings sets
async_requiredon the store, sync calls are rejected by default.Config::async_allow_synclets the embedder opt into managing sync/async dispatch themselves.Design decisions
Socket-based waker (not a callback): A socket fd integrates naturally with any event loop that supports
poll/epoll/kqueue/IOCP. A function pointer callback would require documenting thread-safety constraints and wouldn't integrate with event loops without additional plumbing.Separate function (not modifying
wasmtime_call_future_poll): C doesn't support optional parameters. Adding a parameter would break all existing callers. A new function is non-breaking and follows the existing_asyncsuffix convention.Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform socket helpers, exercises pending→waker fires→ready cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy edited PR #13341:
Closes #12991.
This PR adds two features to the C API for embedders integrating wasmtime with foreign async event loops (e.g. Python's asyncio):
wasmtime_call_future_poll_with_notify— likewasmtime_call_future_poll, but accepts a socket fd. ASocketWakerwrites a byte to the socket when the future can make progress, allowing the host to sleep on the socket instead of busy-polling.
Config::async_allow_sync— permits synchronous calls on stores that have async host functions linked. Intended for embedders that manage sync/async dispatch themselves and know certain exports will never suspend.These are the wasmtime-side changes needed for wasmtime-py async support. A corresponding wasmtime-py PR is in development.
Motivation
We are building Python bindings (wasmtime-py) that call WASM component functions using
asyncio. The component useswasi:httpbacked by wasmtime's internal Tokio runtime. Today,wasmtime_call_future_pollusesWaker::noop(), so the only way to know when to re-poll is busy-polling withasyncio.sleep(0). This wastes CPU and adds latency.With
poll_with_notify, Python creates a socket pair, passes the write fd to wasmtime, and awaits readability on the read end vialoop.sock_recv(). When Tokio completes background I/O and wakes the future, theSocketWakerwrites a byte, waking Python's event loop.For convenience, the same embedder also wants to call non-suspending exports synchronously (avoiding the overhead of setting up a socket pair and poll loop for functions that will complete immediately). Since linking WASI async bindings sets
async_requiredon the store, sync calls are rejected by default.Config::async_allow_synclets the embedder opt into managing sync/async dispatch themselves.Design decisions
- Separate function (not modifying
wasmtime_call_future_poll): C doesn't support optional parameters. Adding a parameter would break all existing callers. A new function is non-breaking.Testing
- Rust integration test in
tests/all/missing_async.rs- C API tests in
crates/c-api/tests/async.cc(cross-platform socket helpers, exercises pending→waker fires→ready cycle)- End-to-end validation via wasmtime-py demo (WASI HTTP call with Tokio background threads)
pgrayy updated PR #13341.
pgrayy updated PR #13341.
pgrayy updated PR #13341.
:cross_mark: pgrayy closed without merge PR #13341.
Last updated: Jun 01 2026 at 09:49 UTC