Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8f172b6
experiment: leave only vibeio
egorsmkv Jul 19, 2026
c14eeb1
replace shell script with Python script for TLS cert generation
egorsmkv Jul 19, 2026
f121141
simplify listener handling in ServerCore for non-linux platforms
egorsmkv Jul 19, 2026
5b7a50b
add workload matrix benchmark for production-shaped network traffic
egorsmkv Jul 19, 2026
afbaf71
implement stream transport write handling and flush logic
egorsmkv Jul 19, 2026
09ae8fd
enhance profiling support and buffer management in stream transport
egorsmkv Jul 20, 2026
0268318
a fix
egorsmkv Jul 20, 2026
9ac4805
enhance websocket message handling and idle connection metrics in ben…
egorsmkv Jul 20, 2026
96a221c
update client connection handling and improve data transfer logic
egorsmkv Jul 20, 2026
f962982
add async-io dependency and optimize socket connection handling
egorsmkv Jul 20, 2026
17bd3ed
optimize IP address resolution by adding __ipaddr_info function to re…
egorsmkv Jul 20, 2026
3fe2c72
implement non-blocking TCP connect handling with vibeio reactor for i…
egorsmkv Jul 20, 2026
66b1871
refactor loop core and runtime to utilize thread-local storage for I/…
egorsmkv Jul 20, 2026
de5905e
implement bounded busy-wait before parking in block_on to reduce late…
egorsmkv Jul 20, 2026
697c454
add inlines
egorsmkv Jul 20, 2026
08656b3
update asyncio task creation to pass running loop for Python 3.8/3.9 …
egorsmkv Jul 20, 2026
f8ab38d
ensure Python sockets remain open until TLS accept workers are woken …
egorsmkv Jul 21, 2026
f8a525d
implement client config caching and update TLS context management
egorsmkv Jul 21, 2026
47e2978
fork vibeio into current repo to make rsloop faster in canceling
egorsmkv Jul 21, 2026
1ac8047
update StopSocketReader command to include done_tx and enhance server…
egorsmkv Jul 21, 2026
8acccff
fix some issues with tls generation and benchmark on macos
egorsmkv Jul 21, 2026
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
399 changes: 2 additions & 397 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ default = []
profiler = ["dep:tracy-client", "profiling/profile-with-tracy"]

[dependencies]
async-io = "2"
async-std = "1"
compio = { version = "0.19", default-features = false, features = ["runtime", "net", "io", "signal", "time", "io-uring"] }
crossbeam-channel = "0.5"
futures = { version = "0.3", default-features = false, features = ["std"] }
libc = "0.2"
Expand All @@ -31,12 +31,12 @@ rustls-native-certs = "0.8"
rustls-pemfile = "2"
signal-hook = "0.4"
socket2 = "0.6"
tokio = { version = "1", default-features = false, features = ["io-util"] }
tracy-client = { version = "0.18", default-features = false, features = ["enable", "only-localhost", "sampling"], optional = true }
vibeio = { path = "vendor/vibeio", default-features = false, features = ["time"] }

[target.'cfg(windows)'.dependencies]
tokio = { version = "1", default-features = false, features = ["io-util"] }
vibeio = { version = "0.2.15", default-features = false }
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Networking_WinSock", "Win32_System_Pipes", "Win32_System_Threading"] }
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Networking_WinSock", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Threading"] }

[build-dependencies]
pyo3-build-config = "0.29"
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
`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).
and I/O work. That thread runs a `vibeio` runtime, using io_uring on Linux,
IOCP on Windows, and mio-backed readiness on other supported platforms. Plain
TCP / Unix socket reads and non-TLS server accepts run on that runtime. 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:

Expand Down Expand Up @@ -207,16 +207,18 @@ is backed by the lower level transport code in

## Runtime Model

Today the runtime is hybrid rather than fully single-threaded:
The runtime is centered on one `vibeio` runtime per loop:

- 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 the `compio` runtime on that thread
- plain TCP / Unix socket reads and non-TLS accept loops use `vibeio` on that
thread across supported platforms
- generic `add_reader` / `add_writer` descriptors use cancellable OS-poll
workers because `vibeio` does not expose arbitrary raw-descriptor registration
- 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.
The runtime dependency is now unified, but the codebase has not finished
eliminating every helper thread yet.

## Current Limitations

Expand All @@ -226,7 +228,7 @@ These gaps are visible in the current implementation.
CPython's OpenSSL-backed `ssl` module. In particular, encrypted private keys
are not supported yet, and the fast-stream monkeypatch still falls back to
stdlib helpers whenever `ssl` is enabled. TLS transport internals also still
use helper-thread paths instead of the newer runtime-thread `compio` socket
use helper-thread paths instead of the runtime-thread `vibeio` socket
path.
- Subprocess support still has one notable gap:
`preexec_fn` remains unsupported because running arbitrary Python between
Expand All @@ -240,8 +242,9 @@ These gaps are visible in the current implementation.
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.
all supported platforms, but generic descriptor watches, writes, and
TLS-heavy paths are not fully collapsed onto that same single-threaded I/O
path yet.

## Build

Expand Down Expand Up @@ -376,10 +379,7 @@ comparison demo.
## Acknowledgements

`rsloop` builds on the Python `asyncio` model and is implemented with
[PyO3](https://pyo3.rs/) on the Rust side. The runtime and I/O work in the
current implementation rely in part on
[compio](https://github.com/compio-rs/compio). On Windows, parts of the
runtime also rely on
[PyO3](https://pyo3.rs/) on the Rust side. Runtime and socket I/O are powered by
[vibeio](https://crates.io/crates/vibeio).

## License
Expand Down
76 changes: 76 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,79 @@ three loops to go through the stdlib `asyncio` streams layer instead, pass:
```bash
uv run --with uvloop python benchmarks/compare_event_loops.py --no-rsloop-fast-streams
```

## Representative workload matrix

[`workload_matrix.py`](./workload_matrix.py) complements the microbenchmarks
with concurrent, production-shaped network traffic. It runs each loop and
scenario in a fresh subprocess and reports throughput, transferred MiB/s,
p50/p95/p99 operation latency, and peak RSS.

The scenarios are:

- `http_keepalive`: concurrent HTTP/1.1 keep-alive clients, configurable
response bodies, and modest application CPU work
- `tls_http`: the same workload over TLS
- `websocket_messages`: concurrent persistent RFC 6455 connections with
masked client frames and mixed message sizes
- `mixed_streams`: concurrent connections cycling through 64 B to 64 KiB
messages
- `bulk_transfer`: concurrent large transfers with `drain()` backpressure
- `idle_connections`: many idle connections activated at the same time

Build rsloop in release mode and run the standard matrix with:

```bash
uv run --with maturin maturin develop --release
uv run --with uvloop python benchmarks/workload_matrix.py # Unix
uv run --with winloop python benchmarks/workload_matrix.py # Windows
```

The default comparison is `asyncio,uvloop,rsloop` on Unix. Because uvloop is
not available on Windows, the default there is `asyncio,winloop,rsloop`.
Unavailable optional loops are reported and skipped.

For a quick smoke run:

```bash
uv run --with uvloop python benchmarks/workload_matrix.py \
--warmups 0 \
--repeat 1 \
--concurrency 4 \
--requests-per-connection 5 \
--bulk-bytes 262144 \
--idle-connections 20 \
--idle-seconds 0.01
```

Use `--json-output benchmarks/results/matrix.json` to retain raw measurements.
TLS uses the test certificates under `tests/fixtures/tls`; regenerate them
cross-platform when needed with:

```bash
uv run --no-project python scripts/generate_test_tls_certs.py tests/fixtures/tls
```

For Tracy, build with the profiler feature and request an unmeasured profiling
pass before each rsloop scenario:

```bash
uv run --with maturin maturin develop --release --features profiler
uv run --with uvloop python benchmarks/workload_matrix.py \
--loops rsloop \
--profile-rsloop-dir benchmarks/profiles \
--allow-profiler-build
```

`--allow-profiler-build` is required because this invocation also reports
measurements from the Tracy-enabled binary. Treat those measurements as
profiling diagnostics, not as comparable release-build results. Rebuild without
`--features profiler` before collecting the normal comparison matrix.

On Windows, replace `--with uvloop` with `--with winloop` in the quick and
profiling commands.

Treat this matrix as a regression and trade-off tool, not a single leaderboard.
Compare throughput together with tail latency and memory, and tune concurrency,
payload sizes, application work, and connection counts to match the target
deployment.
4 changes: 2 additions & 2 deletions benchmarks/compare_event_loops.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
def default_loops_csv() -> str:
loops = ["asyncio", "rsloop"]
if sys.platform == "win32":
loops.insert(2, "winloop")
loops.insert(1, "winloop")
else:
loops.insert(2, "uvloop")
loops.insert(1, "uvloop")
return ",".join(loops)


Expand Down
Loading
Loading