feat: trio support via sniffio dispatch in existing API#82
Open
kollektiv wants to merge 1 commit into
Open
Conversation
Detect the running Python async library at call time (via sniffio) and
dispatch inside the existing entry points, so tokio::future_into_py /
tokio::into_future and the async_std equivalents work unchanged when
called from trio. The asyncio code path is byte-for-byte unchanged.
Design:
- RuntimeKind (#[non_exhaustive]) field on the existing TaskLocals;
TaskLocals::current(py) sniffs the running library. New
TaskLocals::{trio, kind, token} accessors.
- into_future_with_locals, generic::future_into_py_with_locals, and
into_stream_with_locals_v1/v2 dispatch on locals.kind() — the asyncio
arm is the existing body, the trio arm uses src/trio.rs.
- src/trio.rs (private): a RustCoroutine #[pyclass] parks the awaiting
trio task via trio.lowlevel.wait_task_rescheduled and is woken from
the Rust executor thread via TrioToken.run_sync_soon. The user's
future runs on R::spawn; the coroutine awaits a oneshot::Receiver.
- contextvars are propagated into the spawned trio system task via
spawn_system_task(context=...) (requires trio>=0.23).
- No new Cargo features. trio and sniffio are imported lazily; if
absent, everything falls through to the asyncio path.
- local_future_into_py returns NotImplementedError under trio;
get_current_loop returns RuntimeError under trio. run/
run_until_complete remain asyncio-only by design.
asyncio-side fixes picked up along the way:
- into_stream_v2: a raising async generator now closes the stream
promptly (previously relied on SenderGlue GC), and the captured
contextvars.Context is now passed through to the forwarding task.
Testing:
- pytests/test_trio.rs (24 tests) + pytests/test_async_std_trio.rs
exercise the public tokio::* / async_std::* entry points under
trio.run: round-trips, sniffio dispatch, panic propagation,
contextvars, into_future error/BaseException handling, into_stream
v1/v2 (cfg unstable-streams), the local_future_into_py
NotImplementedError, and a move_on_after cancellation regression
test for RustCoroutine.send().
- CI installs trio>=0.23 + sniffio across the matrix (free-threaded
3.13 on Windows excluded — cffi unsupported there).
Member
|
Thanks for the PR. A few comments:
I would prefer to move the main PyO3 support forward first, and then reconsider what APIs make sense to evolve in this crate after. Help on doing so is welcome (but please consider carefully crafted discussion and PRs which are individually reviewable, not AI-generated monoliths). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds trio support to the existing API.
tokio::future_into_py,tokio::into_future, and theasync_stdequivalents now work whether they're called from asyncio or trio. The running async library is detected viasniffioand the right thing happens. Asyncio behavior is unchanged.Why: downstream Python packages built on this crate want to support trio applications without
trio-asyncioshims, and this crate is the layer where the asyncio assumption lives. Temporal's Python SDK is a potential candidate.Approach:
TaskLocalsgains aRuntimeKindfield;TaskLocals::current(py)auto-detects asyncio vs trio.kind. Asyncio takes the existing code path; trio uses a privatesrc/trio.rsmodule that parks/wakes the trio task viatrio.lowlevel.trio/sniffioare optional at runtime; if not installed, everything works as before.New public API:
RuntimeKind,TaskLocals::{current, trio, kind, token}. Everything else is private.Not supported under trio:
local_future_into_py,get_current_loop,run/run_until_complete. These are asyncio-specific by nature and return clear errors.Tests: 24 integration tests in
pytests/test_trio.rscovering round-trips, cancellation, panics, contextvars, and streams; CI runs them across the existing matrix.