rsloop is a PyO3-based asyncio event loop implemented in Rust.
Each rsloop.Loop owns a dedicated Rust runtime thread for loop coordination
and I/O work. On Linux, low-level fd watchers plus plain TCP / Unix socket
readiness, socket reads, and non-TLS server accepts are driven from that thread
through compio with io_uring support enabled. Python callbacks, tasks, and
coroutines still run on the thread that calls run_forever() or
run_until_complete() (usually the main Python thread).
The package exposes:
- a native extension module at
rsloop._loop - a Python wrapper in
python/rsloop/__init__.py rsloop.Loop,rsloop.new_event_loop(), andrsloop.run(...)
Repository metadata currently targets Python >=3.8. The packaged project now
supports the core event-loop surface on Linux, macOS, and Windows, including
Windows pipe transports and subprocess workflows.
From PyPI:
pip install rsloopWith uv:
uv add rsloopSimple entry point:
import rsloop
async def main():
...
rsloop.run(main())Manual loop creation also works:
import asyncio
import rsloop
loop = rsloop.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(...)
finally:
asyncio.set_event_loop(None)
loop.close()Importing rsloop also patches asyncio.set_event_loop() so Python 3.8 can
accept an rsloop.Loop instance, matching the behavior exercised by
tests/test_run.py.
The current codebase implements these user-facing areas.
Loop lifecycle and scheduling:
run_forever,run_until_complete,stop,closetime,is_running,is_closedget_debug,set_debugcall_soon,call_soon_threadsafe,call_later,call_at- returned
HandleandTimerHandleobjects withcancel()/cancelled()
Tasks, futures, and execution helpers:
create_future,create_taskset_task_factory,get_task_factoryset_exception_handler,get_exception_handler,call_exception_handler,default_exception_handlerset_default_executor,run_in_executorshutdown_asyncgens,shutdown_default_executor- callback execution under captured
contextvars.Context asyncio.get_running_loop()support while running onrslooprsloop.run(...)helper, withasyncio.run(..., loop_factory=...)integration on Python 3.12+
I/O and networking:
add_reader,remove_reader,add_writer,remove_writersock_recv,sock_recv_into,sock_sendall,sock_accept,sock_connectgetaddrinfo,getnameinfocreate_server,create_connectioncreate_unix_server,create_unix_connectionconnect_accepted_socket- returned
Serverobjects withclose(),is_serving(),get_loop(), andsockets() - returned
StreamTransportobjects withwrite(),writelines(),close(),abort(),is_closing(),write_eof(),can_write_eof(),get_extra_info(),get_protocol(),set_protocol(),pause_reading(),resume_reading(),is_reading()
Pipes, subprocesses, and signals:
connect_read_pipe,connect_write_pipesubprocess_exec,subprocess_shell- returned
ProcessTransportandProcessPipeTransportobjects - higher-level compatibility with
asyncio.create_subprocess_exec()andasyncio.create_subprocess_shell() - Unix subprocess options including
cwd,env,executable,pass_fds,start_new_session,process_group,user,group,extra_groups,umask, andrestore_signals add_signal_handler,remove_signal_handler
Profiling:
profile(...),profiler_running(),start_profiler(),stop_profiler()
Importing rsloop patches asyncio.open_connection() and
asyncio.start_server() by default.
That import-time behavior is controlled by RSLOOP_USE_FAST_STREAMS and can be
disabled with:
export RSLOOP_USE_FAST_STREAMS=0The native fast-stream path is used only when:
- the running loop is an
rsloop.Loop sslis unset orNone
Otherwise rsloop falls back to the stdlib asyncio.streams helpers.
The implementation lives in src/fast_streams.rs and
is backed by the lower level transport code in
src/stream_transport.rs.
Today the runtime is hybrid rather than fully single-threaded:
- the loop coordination thread is always the central scheduler
- on Linux,
add_reader/add_writer, plain socket reads, and non-TLS socket accept loops use thecompioruntime on that thread - some transport paths still fall back to helper threads, especially TLS I/O, TLS server accept, and parts of the legacy transport write path
That means the codebase has started the move toward a single-runtime-thread I/O model, but has not finished eliminating every helper thread yet.
These gaps are visible in the current implementation.
- TLS uses a
rustlsbackend with a narrower compatibility surface than CPython's OpenSSL-backedsslmodule. In particular, encrypted private keys are not supported yet, and the fast-stream monkeypatch still falls back to stdlib helpers wheneversslis enabled. TLS transport internals also still use helper-thread paths instead of the newer runtime-threadcompiosocket path. - Subprocess support still has one notable gap:
preexec_fnremains unsupported because running arbitrary Python betweenfork()andexec()is unsafe in this runtime model. - Unix-specific APIs remain Unix-specific:
create_unix_server,create_unix_connection,add_signal_handler,remove_signal_handler. - Platform-specific limitations still apply:
Unix socket APIs and Unix signal handlers remain Unix-only, and several
subprocess options such as
pass_fds,user,group, andumaskare still specific to Unix process spawning. - The transport runtime model is still in transition: plain socket reads and non-TLS accepts now run on the loop runtime thread on Linux, but writes and TLS-heavy paths are not fully collapsed onto that same single-threaded I/O path yet.
Quick check:
cargo checkRelease build and editable install:
cargo build --release
uv run --with maturin maturin develop --releaseBuild release wheels into dist/wheels:
scripts/build-wheels.shscripts/build-wheels.sh currently defaults to
CPython 3.8 3.9 3.10 3.11 3.12 3.13 3.14 plus free-threaded 3.14t, and
uses uv python install / uv python find to locate interpreters.
Profiling is behind the Cargo feature profiler and is disabled by default.
Build or install with that feature first:
cargo build --release --features profiler
uv run --with maturin maturin develop --release --features profilerThen wrap the code you want to inspect:
import rsloop
with rsloop.profile("rsloop-flamegraph.svg", frequency=999):
rsloop.run(main())Or manage the session manually:
import rsloop
rsloop.start_profiler(frequency=999)
try:
rsloop.run(main())
finally:
rsloop.stop_profiler("rsloop-flamegraph.svg")Only format="flamegraph" is currently implemented. If the extension was built
without --features profiler, profile() and start_profiler() raise a
runtime error.
Run the repository examples from the project root:
uv run python examples/01_basics.py
uv run python examples/02_fd_and_sockets.py
uv run python examples/03_streams.py
uv run python examples/04_unix_and_accepted_socket.py
uv run python examples/05_pipes_signals_subprocesses.pyExample files:
examples/01_basics.py,
examples/02_fd_and_sockets.py,
examples/03_streams.py,
examples/04_unix_and_accepted_socket.py,
examples/05_pipes_signals_subprocesses.py.
The repository also includes:
demo/fastapi_service.pyfor running the same FastAPI app on stdlibasyncio,uvloop, orrsloopbenchmarks/compare_event_loops.pyfor callback, task, and TCP stream comparisons
uv run --with maturin maturin develop --release
uv run --with uvloop python benchmarks/compare_event_loops.pyAn example output from that script on Linux with CPython 3.14.0:
callbacks (200,000 ops)
loop median_s best_s ops_per_s vs_fastest
rsloop 0.041608 0.040585 4,806,807 1.00x
uvloop 0.087539 0.086690 2,284,707 2.10x
asyncio 0.229563 0.221348 871,222 5.52x
tasks (50,000 ops)
loop median_s best_s ops_per_s vs_fastest
uvloop 0.084425 0.083497 592,239 1.00x
rsloop 0.091845 0.090982 544,397 1.09x
asyncio 0.138782 0.137716 360,276 1.64x
tcp_streams mode: rsloop native fast streams
tcp_streams (5,000 ops)
loop median_s best_s ops_per_s vs_fastest
rsloop 0.119483 0.118451 41,847 1.00x
uvloop 0.119582 0.116446 41,812 1.00x
asyncio 0.138408 0.134438 36,125 1.16x
See benchmarks/README.md for workload details and
extra flags, and demo/README.md for the FastAPI loop
comparison demo.
rsloop builds on the Python asyncio model and is implemented with
PyO3 on the Rust side. The runtime and I/O work in the
current implementation rely in part on
compio.
This project is licensed under the Apache License, Version 2.0. See
LICENSE for the full text.
