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.run(main())Or manage the session manually:
import rsloop
rsloop.start_profiler()
try:
rsloop.run(main())
finally:
rsloop.stop_profiler()This starts a Tracy client inside the process. Build a release binary, open
tracy-profiler.exe, then connect to the running process while the profiled
code is executing.
The current Tracy feature set is aimed at local Windows profiling:
enable, only-localhost, sampling, and flush-on-exit. The last one helps
short-lived runs flush data before exit.
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 (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. On Windows, parts of the
runtime also rely on
vibeio.
This project is licensed under the Apache License, Version 2.0. See
LICENSE for the full text.
