rsloop 0.1.23__tar.gz → 0.1.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rsloop-0.1.23 → rsloop-0.1.25}/Cargo.lock +31 -31
- {rsloop-0.1.23 → rsloop-0.1.25}/Cargo.toml +2 -2
- {rsloop-0.1.23 → rsloop-0.1.25}/PKG-INFO +14 -14
- {rsloop-0.1.23 → rsloop-0.1.25}/README.md +13 -13
- {rsloop-0.1.23 → rsloop-0.1.25}/benchmarks/README.md +1 -1
- {rsloop-0.1.23 → rsloop-0.1.25}/benchmarks/compare_event_loops.py +6 -3
- {rsloop-0.1.23 → rsloop-0.1.25}/pyproject.toml +1 -1
- {rsloop-0.1.23 → rsloop-0.1.25}/src/callbacks.rs +16 -22
- {rsloop-0.1.23 → rsloop-0.1.25}/src/fast_streams.rs +33 -20
- {rsloop-0.1.23 → rsloop-0.1.25}/src/fd_ops.rs +11 -1
- {rsloop-0.1.23 → rsloop-0.1.25}/src/loop_core/commands.rs +10 -10
- {rsloop-0.1.23 → rsloop-0.1.25}/src/loop_core.rs +164 -140
- rsloop-0.1.25/src/python_api/pre_exec.rs +118 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/python_api.rs +150 -53
- {rsloop-0.1.23 → rsloop-0.1.25}/src/runtime.rs +46 -141
- {rsloop-0.1.23 → rsloop-0.1.25}/src/stream_transport.rs +161 -30
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/test_compat.py +76 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/uv.lock +410 -281
- rsloop-0.1.23/src/python_api/pre_exec.rs +0 -109
- {rsloop-0.1.23 → rsloop-0.1.25}/.cargo/config.toml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/.github/workflows/mkdocs.yml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/.github/workflows/tests.yml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/.github/workflows/wheels.yml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/.gitignore +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/.vscode/settings.json +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/LICENSE +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/build.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/demo/README.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/demo/fastapi_service.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/demo/picows_server.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/demo/picows_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/demo/wsbench_websockets.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/development.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/examples.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/getting-started.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/how-it-works.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/index.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/project-structure.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/docs/rust-extensions.md +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/examples/01_basics.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/examples/02_fd_and_sockets.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/examples/03_streams.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/examples/04_unix_and_accepted_socket.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/examples/05_pipes_signals_subprocesses.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/justfile +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/mkdocs.yml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/python/rsloop/__init__.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/python/rsloop/_bootstrap.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/python/rsloop/_loop_compat.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/python/rsloop/_profile.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/python/rsloop/_run.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/qualirs.toml +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/rsloop.png +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/scripts/build-wheels.sh +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/scripts/generate-test-tls-certs.sh +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/scripts/python-versions.sh +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/scripts/test-supported-pythons.sh +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/async_event.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/blocking.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/context.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/errors.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/fd_ops/windows.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/lib.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/module_init.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/process_transport.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/profiler.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/python_api/ffi_helpers.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/python_api/process_handles.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/python_names.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/runtime/timer_entry.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/rust_async.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/tls/material.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/tls.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/src/windows_vibeio.rs +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/_smoke.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/aiohttp_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/django_asgi_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/falcon_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/fastapi_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/faststream_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/litestar_granian_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/litestar_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/quart_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/sanic_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/starlette_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/packages/uvicorn_test.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/test_run.py +0 -0
- {rsloop-0.1.23 → rsloop-0.1.25}/tests/test_tls.py +0 -0
|
@@ -234,9 +234,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
|
234
234
|
|
|
235
235
|
[[package]]
|
|
236
236
|
name = "bytes"
|
|
237
|
-
version = "1.12.
|
|
237
|
+
version = "1.12.1"
|
|
238
238
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
239
|
-
checksum = "
|
|
239
|
+
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
240
240
|
|
|
241
241
|
[[package]]
|
|
242
242
|
name = "cc"
|
|
@@ -476,18 +476,18 @@ dependencies = [
|
|
|
476
476
|
|
|
477
477
|
[[package]]
|
|
478
478
|
name = "crossbeam-queue"
|
|
479
|
-
version = "0.3.
|
|
479
|
+
version = "0.3.13"
|
|
480
480
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
481
|
-
checksum = "
|
|
481
|
+
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
|
|
482
482
|
dependencies = [
|
|
483
483
|
"crossbeam-utils",
|
|
484
484
|
]
|
|
485
485
|
|
|
486
486
|
[[package]]
|
|
487
487
|
name = "crossbeam-utils"
|
|
488
|
-
version = "0.8.
|
|
488
|
+
version = "0.8.22"
|
|
489
489
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
490
|
-
checksum = "
|
|
490
|
+
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
|
491
491
|
|
|
492
492
|
[[package]]
|
|
493
493
|
name = "dunce"
|
|
@@ -581,9 +581,9 @@ dependencies = [
|
|
|
581
581
|
|
|
582
582
|
[[package]]
|
|
583
583
|
name = "futures-channel"
|
|
584
|
-
version = "0.3.
|
|
584
|
+
version = "0.3.33"
|
|
585
585
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
586
|
-
checksum = "
|
|
586
|
+
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
|
587
587
|
dependencies = [
|
|
588
588
|
"futures-core",
|
|
589
589
|
"futures-sink",
|
|
@@ -591,15 +591,15 @@ dependencies = [
|
|
|
591
591
|
|
|
592
592
|
[[package]]
|
|
593
593
|
name = "futures-core"
|
|
594
|
-
version = "0.3.
|
|
594
|
+
version = "0.3.33"
|
|
595
595
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
596
|
-
checksum = "
|
|
596
|
+
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
|
597
597
|
|
|
598
598
|
[[package]]
|
|
599
599
|
name = "futures-io"
|
|
600
|
-
version = "0.3.
|
|
600
|
+
version = "0.3.33"
|
|
601
601
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
602
|
-
checksum = "
|
|
602
|
+
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
|
603
603
|
|
|
604
604
|
[[package]]
|
|
605
605
|
name = "futures-lite"
|
|
@@ -616,9 +616,9 @@ dependencies = [
|
|
|
616
616
|
|
|
617
617
|
[[package]]
|
|
618
618
|
name = "futures-macro"
|
|
619
|
-
version = "0.3.
|
|
619
|
+
version = "0.3.33"
|
|
620
620
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
621
|
-
checksum = "
|
|
621
|
+
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
|
622
622
|
dependencies = [
|
|
623
623
|
"proc-macro2",
|
|
624
624
|
"quote",
|
|
@@ -627,21 +627,21 @@ dependencies = [
|
|
|
627
627
|
|
|
628
628
|
[[package]]
|
|
629
629
|
name = "futures-sink"
|
|
630
|
-
version = "0.3.
|
|
630
|
+
version = "0.3.33"
|
|
631
631
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
632
|
-
checksum = "
|
|
632
|
+
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
|
|
633
633
|
|
|
634
634
|
[[package]]
|
|
635
635
|
name = "futures-task"
|
|
636
|
-
version = "0.3.
|
|
636
|
+
version = "0.3.33"
|
|
637
637
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
638
|
-
checksum = "
|
|
638
|
+
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
|
639
639
|
|
|
640
640
|
[[package]]
|
|
641
641
|
name = "futures-util"
|
|
642
|
-
version = "0.3.
|
|
642
|
+
version = "0.3.33"
|
|
643
643
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
644
|
-
checksum = "
|
|
644
|
+
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
|
645
645
|
dependencies = [
|
|
646
646
|
"futures-channel",
|
|
647
647
|
"futures-core",
|
|
@@ -842,15 +842,15 @@ dependencies = [
|
|
|
842
842
|
|
|
843
843
|
[[package]]
|
|
844
844
|
name = "memchr"
|
|
845
|
-
version = "2.8.
|
|
845
|
+
version = "2.8.3"
|
|
846
846
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
847
|
-
checksum = "
|
|
847
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
848
848
|
|
|
849
849
|
[[package]]
|
|
850
850
|
name = "mio"
|
|
851
|
-
version = "1.2.
|
|
851
|
+
version = "1.2.2"
|
|
852
852
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
853
|
-
checksum = "
|
|
853
|
+
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
|
854
854
|
dependencies = [
|
|
855
855
|
"libc",
|
|
856
856
|
"log",
|
|
@@ -1116,7 +1116,7 @@ dependencies = [
|
|
|
1116
1116
|
|
|
1117
1117
|
[[package]]
|
|
1118
1118
|
name = "rsloop"
|
|
1119
|
-
version = "0.1.
|
|
1119
|
+
version = "0.1.25"
|
|
1120
1120
|
dependencies = [
|
|
1121
1121
|
"async-std",
|
|
1122
1122
|
"compio",
|
|
@@ -1384,9 +1384,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
|
1384
1384
|
|
|
1385
1385
|
[[package]]
|
|
1386
1386
|
name = "syn"
|
|
1387
|
-
version = "2.0.
|
|
1387
|
+
version = "2.0.119"
|
|
1388
1388
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1389
|
-
checksum = "
|
|
1389
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
1390
1390
|
dependencies = [
|
|
1391
1391
|
"proc-macro2",
|
|
1392
1392
|
"quote",
|
|
@@ -1431,9 +1431,9 @@ dependencies = [
|
|
|
1431
1431
|
|
|
1432
1432
|
[[package]]
|
|
1433
1433
|
name = "tokio"
|
|
1434
|
-
version = "1.
|
|
1434
|
+
version = "1.53.0"
|
|
1435
1435
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1436
|
-
checksum = "
|
|
1436
|
+
checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
|
|
1437
1437
|
dependencies = [
|
|
1438
1438
|
"bytes",
|
|
1439
1439
|
"pin-project-lite",
|
|
@@ -1541,9 +1541,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
|
1541
1541
|
|
|
1542
1542
|
[[package]]
|
|
1543
1543
|
name = "vibeio"
|
|
1544
|
-
version = "0.2.
|
|
1544
|
+
version = "0.2.15"
|
|
1545
1545
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1546
|
-
checksum = "
|
|
1546
|
+
checksum = "63b968e30d91bc5c14d23f9af184f26ea7f0d886ab309d11b37148b244d7dfa4"
|
|
1547
1547
|
dependencies = [
|
|
1548
1548
|
"crossbeam-queue",
|
|
1549
1549
|
"futures-util",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rsloop"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.25"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
description = "An event loop for asyncio written in Rust"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -35,7 +35,7 @@ tracy-client = { version = "0.18", default-features = false, features = ["enable
|
|
|
35
35
|
|
|
36
36
|
[target.'cfg(windows)'.dependencies]
|
|
37
37
|
tokio = { version = "1", default-features = false, features = ["io-util"] }
|
|
38
|
-
vibeio = { version = "0.2.
|
|
38
|
+
vibeio = { version = "0.2.15", default-features = false }
|
|
39
39
|
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Networking_WinSock", "Win32_System_Pipes", "Win32_System_Threading"] }
|
|
40
40
|
|
|
41
41
|
[build-dependencies]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rsloop
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -379,26 +379,26 @@ uv run --with maturin maturin develop --release
|
|
|
379
379
|
uv run --with uvloop python benchmarks/compare_event_loops.py
|
|
380
380
|
```
|
|
381
381
|
|
|
382
|
-
An example output from that script on
|
|
382
|
+
An example output from that script on macOS (arm64) with CPython 3.14:
|
|
383
383
|
|
|
384
384
|
```
|
|
385
385
|
callbacks (200,000 ops)
|
|
386
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
387
|
-
rsloop 0.
|
|
388
|
-
uvloop 0.
|
|
389
|
-
asyncio 0.
|
|
386
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
387
|
+
rsloop 0.033083 0.032710 6,045,401 67.5 MiB 1.00x
|
|
388
|
+
uvloop 0.040958 0.040721 4,883,026 72.8 MiB 1.24x
|
|
389
|
+
asyncio 0.082233 0.082093 2,432,114 65.3 MiB 2.49x
|
|
390
390
|
|
|
391
391
|
tasks (50,000 ops)
|
|
392
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
asyncio 0.
|
|
392
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
393
|
+
rsloop 0.063593 0.063286 786,247 37.6 MiB 1.00x
|
|
394
|
+
uvloop 0.069614 0.069420 718,251 38.4 MiB 1.09x
|
|
395
|
+
asyncio 0.108114 0.107502 462,473 36.1 MiB 1.70x
|
|
396
396
|
|
|
397
397
|
tcp_streams (5,000 ops)
|
|
398
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
399
|
-
rsloop 0.
|
|
400
|
-
uvloop 0.
|
|
401
|
-
asyncio 0.
|
|
398
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
399
|
+
rsloop 0.090940 0.083355 54,981 32.2 MiB 1.00x
|
|
400
|
+
uvloop 0.133182 0.127404 37,543 31.5 MiB 1.46x
|
|
401
|
+
asyncio 0.302337 0.299813 16,538 29.6 MiB 3.32x
|
|
402
402
|
```
|
|
403
403
|
|
|
404
404
|
See [`benchmarks/README.md`](./benchmarks/README.md) for workload details and
|
|
@@ -347,26 +347,26 @@ uv run --with maturin maturin develop --release
|
|
|
347
347
|
uv run --with uvloop python benchmarks/compare_event_loops.py
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
-
An example output from that script on
|
|
350
|
+
An example output from that script on macOS (arm64) with CPython 3.14:
|
|
351
351
|
|
|
352
352
|
```
|
|
353
353
|
callbacks (200,000 ops)
|
|
354
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
355
|
-
rsloop 0.
|
|
356
|
-
uvloop 0.
|
|
357
|
-
asyncio 0.
|
|
354
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
355
|
+
rsloop 0.033083 0.032710 6,045,401 67.5 MiB 1.00x
|
|
356
|
+
uvloop 0.040958 0.040721 4,883,026 72.8 MiB 1.24x
|
|
357
|
+
asyncio 0.082233 0.082093 2,432,114 65.3 MiB 2.49x
|
|
358
358
|
|
|
359
359
|
tasks (50,000 ops)
|
|
360
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
asyncio 0.
|
|
360
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
361
|
+
rsloop 0.063593 0.063286 786,247 37.6 MiB 1.00x
|
|
362
|
+
uvloop 0.069614 0.069420 718,251 38.4 MiB 1.09x
|
|
363
|
+
asyncio 0.108114 0.107502 462,473 36.1 MiB 1.70x
|
|
364
364
|
|
|
365
365
|
tcp_streams (5,000 ops)
|
|
366
|
-
loop median_s best_s ops_per_s vs_fastest
|
|
367
|
-
rsloop 0.
|
|
368
|
-
uvloop 0.
|
|
369
|
-
asyncio 0.
|
|
366
|
+
loop median_s best_s ops_per_s peak_rss vs_fastest
|
|
367
|
+
rsloop 0.090940 0.083355 54,981 32.2 MiB 1.00x
|
|
368
|
+
uvloop 0.133182 0.127404 37,543 31.5 MiB 1.46x
|
|
369
|
+
asyncio 0.302337 0.299813 16,538 29.6 MiB 3.32x
|
|
370
370
|
```
|
|
371
371
|
|
|
372
372
|
See [`benchmarks/README.md`](./benchmarks/README.md) for workload details and
|
|
@@ -57,7 +57,7 @@ The runner executes each loop/workload in a fresh subprocess and reports:
|
|
|
57
57
|
|
|
58
58
|
Current workloads:
|
|
59
59
|
|
|
60
|
-
- `callbacks`:
|
|
60
|
+
- `callbacks`: pre-scheduled batch of `call_soon()` callbacks (schedule + dispatch cost)
|
|
61
61
|
- `tasks`: many tiny `asyncio.sleep(0)` tasks
|
|
62
62
|
- `tcp_streams`: local `asyncio.start_server()` / `asyncio.open_connection()` echo round trips
|
|
63
63
|
|
|
@@ -294,6 +294,10 @@ def run_with_loop(loop_name: str, coro: asyncio.coroutines) -> ChildResult:
|
|
|
294
294
|
|
|
295
295
|
|
|
296
296
|
async def bench_callbacks(loop_name: str, iterations: int) -> ChildResult:
|
|
297
|
+
# Pre-schedule the full batch instead of chaining callbacks: a chain
|
|
298
|
+
# forces asyncio/uvloop into one event-loop iteration (including a
|
|
299
|
+
# kernel poll) per callback, which measures selector overhead rather
|
|
300
|
+
# than call_soon dispatch cost.
|
|
297
301
|
loop = asyncio.get_running_loop()
|
|
298
302
|
done = loop.create_future()
|
|
299
303
|
remaining = iterations
|
|
@@ -303,11 +307,10 @@ async def bench_callbacks(loop_name: str, iterations: int) -> ChildResult:
|
|
|
303
307
|
remaining -= 1
|
|
304
308
|
if remaining == 0:
|
|
305
309
|
done.set_result(None)
|
|
306
|
-
return
|
|
307
|
-
loop.call_soon(callback)
|
|
308
310
|
|
|
309
311
|
start = time.perf_counter()
|
|
310
|
-
|
|
312
|
+
for _ in range(iterations):
|
|
313
|
+
loop.call_soon(callback)
|
|
311
314
|
await done
|
|
312
315
|
return ChildResult(
|
|
313
316
|
loop_name,
|
|
@@ -60,7 +60,6 @@ pub struct ReadyCallback {
|
|
|
60
60
|
kind: CallbackKind,
|
|
61
61
|
callback: Py<PyAny>,
|
|
62
62
|
args: CallbackArgs,
|
|
63
|
-
args_tuple: Py<PyTuple>,
|
|
64
63
|
context: Py<PyAny>,
|
|
65
64
|
context_needs_run: bool,
|
|
66
65
|
cancelled: AtomicBool,
|
|
@@ -94,7 +93,6 @@ impl ReadyCallback {
|
|
|
94
93
|
kind,
|
|
95
94
|
callback,
|
|
96
95
|
args,
|
|
97
|
-
args_tuple,
|
|
98
96
|
context,
|
|
99
97
|
context_needs_run,
|
|
100
98
|
cancelled: AtomicBool::new(false),
|
|
@@ -159,10 +157,6 @@ impl ReadyCallback {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
pub fn clone_args_tuple(&self, py: Python<'_>) -> Py<PyTuple> {
|
|
163
|
-
self.args_tuple.clone_ref(py)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
160
|
#[inline]
|
|
167
161
|
pub fn cancelled(&self) -> bool {
|
|
168
162
|
self.cancelled.load(Ordering::Relaxed)
|
|
@@ -174,42 +168,42 @@ impl ReadyCallback {
|
|
|
174
168
|
}
|
|
175
169
|
}
|
|
176
170
|
|
|
177
|
-
|
|
171
|
+
// The handle owns its callback inline (instead of a separate
|
|
172
|
+
// `Arc<ReadyCallback>` allocation) and the ready queue holds `Py<PyHandle>`
|
|
173
|
+
// clones: one allocation per call_soon instead of two. `frozen` lets the
|
|
174
|
+
// event loop read the callback through `Py::get` without borrow checking.
|
|
175
|
+
#[pyclass(name = "Handle", module = "rsloop._loop", weakref, frozen)]
|
|
178
176
|
pub struct PyHandle {
|
|
179
|
-
|
|
180
|
-
callback: Weak<ReadyCallback>,
|
|
181
|
-
cancelled: AtomicBool,
|
|
177
|
+
callback: ReadyCallback,
|
|
182
178
|
}
|
|
183
179
|
|
|
184
180
|
impl PyHandle {
|
|
185
181
|
#[inline]
|
|
186
|
-
pub fn new(
|
|
187
|
-
Self {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
182
|
+
pub fn new(callback: ReadyCallback) -> Self {
|
|
183
|
+
Self { callback }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[inline]
|
|
187
|
+
pub fn ready(&self) -> &ReadyCallback {
|
|
188
|
+
&self.callback
|
|
192
189
|
}
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
#[pymethods]
|
|
196
193
|
impl PyHandle {
|
|
197
194
|
fn cancel(&self) -> PyResult<()> {
|
|
198
|
-
self.
|
|
199
|
-
if let Some(callback) = self.callback.upgrade() {
|
|
200
|
-
callback.cancel();
|
|
201
|
-
}
|
|
195
|
+
self.callback.cancel();
|
|
202
196
|
Ok(())
|
|
203
197
|
}
|
|
204
198
|
|
|
205
199
|
fn cancelled(&self) -> bool {
|
|
206
|
-
self.cancelled
|
|
200
|
+
self.callback.cancelled()
|
|
207
201
|
}
|
|
208
202
|
|
|
209
203
|
fn __repr__(&self) -> String {
|
|
210
204
|
format!(
|
|
211
205
|
"<Handle id={} cancelled={}>",
|
|
212
|
-
self.
|
|
206
|
+
self.callback.id(),
|
|
213
207
|
self.cancelled()
|
|
214
208
|
)
|
|
215
209
|
}
|
|
@@ -5,10 +5,19 @@ use pyo3_async_runtimes::TaskLocals;
|
|
|
5
5
|
|
|
6
6
|
use crate::python_api::PyLoop;
|
|
7
7
|
use crate::python_names;
|
|
8
|
-
use crate::stream_transport::task_locals_for_loop;
|
|
8
|
+
use crate::stream_transport::{PyStreamTransport, task_locals_for_loop};
|
|
9
9
|
|
|
10
10
|
const DEFAULT_STREAM_LIMIT: usize = 65_536;
|
|
11
11
|
|
|
12
|
+
/// Create a future on `loop_obj`, skipping the Python-level method dispatch
|
|
13
|
+
/// when the loop is a native rsloop instance running on this thread.
|
|
14
|
+
fn loop_create_future(py: Python<'_>, loop_obj: &Py<PyAny>) -> PyResult<Py<PyAny>> {
|
|
15
|
+
if let Some(future) = crate::python_api::try_fast_create_future(py, loop_obj)? {
|
|
16
|
+
return Ok(future);
|
|
17
|
+
}
|
|
18
|
+
python_names::call_method0(py, loop_obj.bind(py), python_names::create_future(py))
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
struct ReadBuffer {
|
|
13
22
|
bytes: Vec<u8>,
|
|
14
23
|
start: usize,
|
|
@@ -167,7 +176,7 @@ impl PyFastStreamReader {
|
|
|
167
176
|
|
|
168
177
|
#[inline]
|
|
169
178
|
fn create_future(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
|
170
|
-
|
|
179
|
+
loop_create_future(py, &self.loop_obj)
|
|
171
180
|
}
|
|
172
181
|
|
|
173
182
|
fn ready_result_future(&self, py: Python<'_>, value: Py<PyAny>) -> PyResult<Py<PyAny>> {
|
|
@@ -235,7 +244,15 @@ impl PyFastStreamReader {
|
|
|
235
244
|
}
|
|
236
245
|
|
|
237
246
|
fn maybe_pause_transport(&mut self, py: Python<'_>) -> PyResult<()> {
|
|
238
|
-
|
|
247
|
+
// Never pause while a coroutine is waiting for data: a pending
|
|
248
|
+
// readexactly()/read() waiter may need more than 2 * limit bytes to
|
|
249
|
+
// complete, and pausing would starve it forever. This mirrors
|
|
250
|
+
// asyncio.StreamReader._wait_for_data, which resumes the transport
|
|
251
|
+
// while a reader is waiting.
|
|
252
|
+
if self.waiter.is_none()
|
|
253
|
+
&& !self.transport.bind(py).is_none()
|
|
254
|
+
&& !self.paused
|
|
255
|
+
&& self.buffer.len() > 2 * self.limit
|
|
239
256
|
{
|
|
240
257
|
match python_names::call_method0(
|
|
241
258
|
py,
|
|
@@ -541,10 +558,8 @@ impl PyFastStreamProtocol {
|
|
|
541
558
|
reader: Py<PyFastStreamReader>,
|
|
542
559
|
client_connected_cb: Py<PyAny>,
|
|
543
560
|
) -> PyResult<Self> {
|
|
544
|
-
let closed =
|
|
545
|
-
|
|
546
|
-
let ready_none =
|
|
547
|
-
python_names::call_method0(py, loop_obj.bind(py), python_names::create_future(py))?;
|
|
561
|
+
let closed = loop_create_future(py, &loop_obj)?;
|
|
562
|
+
let ready_none = loop_create_future(py, &loop_obj)?;
|
|
548
563
|
python_names::call_method1(
|
|
549
564
|
py,
|
|
550
565
|
ready_none.bind(py),
|
|
@@ -578,11 +593,7 @@ impl PyFastStreamProtocol {
|
|
|
578
593
|
}
|
|
579
594
|
|
|
580
595
|
fn ready_exception_future(&self, py: Python<'_>, exc: Py<PyAny>) -> PyResult<Py<PyAny>> {
|
|
581
|
-
let future =
|
|
582
|
-
py,
|
|
583
|
-
self.loop_obj.bind(py),
|
|
584
|
-
python_names::create_future(py),
|
|
585
|
-
)?;
|
|
596
|
+
let future = loop_create_future(py, &self.loop_obj)?;
|
|
586
597
|
python_names::call_method1(
|
|
587
598
|
py,
|
|
588
599
|
future.bind(py),
|
|
@@ -593,11 +604,7 @@ impl PyFastStreamProtocol {
|
|
|
593
604
|
}
|
|
594
605
|
|
|
595
606
|
fn push_drain_waiter(&mut self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
|
596
|
-
let future =
|
|
597
|
-
py,
|
|
598
|
-
self.loop_obj.bind(py),
|
|
599
|
-
python_names::create_future(py),
|
|
600
|
-
)?;
|
|
607
|
+
let future = loop_create_future(py, &self.loop_obj)?;
|
|
601
608
|
self.drain_waiters.push(future.clone_ref(py));
|
|
602
609
|
Ok(future)
|
|
603
610
|
}
|
|
@@ -832,6 +839,11 @@ impl PyFastStreamWriter {
|
|
|
832
839
|
}
|
|
833
840
|
|
|
834
841
|
fn write(&self, py: Python<'_>, data: &Bound<'_, PyAny>) -> PyResult<()> {
|
|
842
|
+
// Native transports take the direct Rust path; only foreign
|
|
843
|
+
// transports go through Python method dispatch.
|
|
844
|
+
if let Ok(transport) = self.transport.bind(py).cast_exact::<PyStreamTransport>() {
|
|
845
|
+
return transport.borrow().write_data(py, data);
|
|
846
|
+
}
|
|
835
847
|
self.transport.call_method1(py, "write", (data,))?;
|
|
836
848
|
Ok(())
|
|
837
849
|
}
|
|
@@ -1003,9 +1015,10 @@ fn native_stream_loop(
|
|
|
1003
1015
|
}
|
|
1004
1016
|
if let Some(kwargs) = kwargs
|
|
1005
1017
|
&& let Some(ssl) = kwargs.get_item("ssl")?
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1018
|
+
&& !ssl.is_none()
|
|
1019
|
+
{
|
|
1020
|
+
return Ok(None);
|
|
1021
|
+
}
|
|
1009
1022
|
Ok(Some(loop_obj))
|
|
1010
1023
|
}
|
|
1011
1024
|
|
|
@@ -85,7 +85,17 @@ pub fn poll_fd(fd: RawFd, read: bool, write: bool, timeout_ms: i32) -> io::Resul
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
let revents = pollfd.revents as i32;
|
|
88
|
-
|
|
88
|
+
// POLLNVAL means the descriptor is not open (e.g. the socket was closed
|
|
89
|
+
// out from under the watcher). Reporting it as "ready" would make
|
|
90
|
+
// watchers fire forever on a dead fd, so surface it as an error instead;
|
|
91
|
+
// a closed fd produces no readiness events on asyncio either.
|
|
92
|
+
if revents & (libc::POLLNVAL as i32) != 0 {
|
|
93
|
+
return Err(io::Error::new(
|
|
94
|
+
io::ErrorKind::InvalidInput,
|
|
95
|
+
"file descriptor is not open",
|
|
96
|
+
));
|
|
97
|
+
}
|
|
98
|
+
let error_bits = (libc::POLLERR | libc::POLLHUP) as i32;
|
|
89
99
|
Ok((
|
|
90
100
|
read && (revents & ((libc::POLLIN as i32) | error_bits)) != 0,
|
|
91
101
|
write && (revents & ((libc::POLLOUT as i32) | error_bits)) != 0,
|
|
@@ -3,9 +3,8 @@ use std::sync::{Arc, Mutex};
|
|
|
3
3
|
use std::time::Instant;
|
|
4
4
|
|
|
5
5
|
use pyo3::prelude::*;
|
|
6
|
-
use pyo3::types::PyTuple;
|
|
7
6
|
|
|
8
|
-
use crate::callbacks::ReadyCallback;
|
|
7
|
+
use crate::callbacks::{PyHandle, ReadyCallback};
|
|
9
8
|
use crate::fd_ops::RawFd;
|
|
10
9
|
use crate::process_transport::ProcessTransportCore;
|
|
11
10
|
use crate::stream_transport::{
|
|
@@ -14,6 +13,7 @@ use crate::stream_transport::{
|
|
|
14
13
|
|
|
15
14
|
pub enum ReadyItem {
|
|
16
15
|
Callback(Arc<ReadyCallback>),
|
|
16
|
+
HandleCallback(Py<PyHandle>),
|
|
17
17
|
FutureSetResult {
|
|
18
18
|
future: Py<PyAny>,
|
|
19
19
|
value: Py<PyAny>,
|
|
@@ -33,6 +33,7 @@ pub enum ReadyItem {
|
|
|
33
33
|
|
|
34
34
|
pub enum LoopCommand {
|
|
35
35
|
ScheduleReady(Arc<ReadyCallback>),
|
|
36
|
+
ScheduleReadyHandle(Py<PyHandle>),
|
|
36
37
|
ScheduleTimer {
|
|
37
38
|
callback: Arc<ReadyCallback>,
|
|
38
39
|
when: Instant,
|
|
@@ -50,6 +51,7 @@ pub enum LoopRunCommand {
|
|
|
50
51
|
EnterRun {
|
|
51
52
|
pending_ready: Arc<Mutex<VecDeque<ReadyItem>>>,
|
|
52
53
|
wake_tx: std::sync::mpsc::Sender<()>,
|
|
54
|
+
wake_pending: Arc<std::sync::atomic::AtomicBool>,
|
|
53
55
|
},
|
|
54
56
|
FinishRun {
|
|
55
57
|
done_tx: std::sync::mpsc::Sender<()>,
|
|
@@ -63,20 +65,18 @@ pub enum LoopSignalCommand {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
pub enum LoopIoCommand {
|
|
68
|
+
// Fd watches hold one persistent ReadyCallback per registration, shared
|
|
69
|
+
// with LoopState's keepalive map. Every readiness event schedules the
|
|
70
|
+
// same callback, so remove_reader()/remove_writer() can cancel pending
|
|
71
|
+
// fires exactly like asyncio's Handle.cancel() on its reader handle.
|
|
66
72
|
StartReader {
|
|
67
73
|
fd: RawFd,
|
|
68
|
-
callback:
|
|
69
|
-
args: Py<PyTuple>,
|
|
70
|
-
context: Py<PyAny>,
|
|
71
|
-
context_needs_run: bool,
|
|
74
|
+
callback: Arc<ReadyCallback>,
|
|
72
75
|
},
|
|
73
76
|
StopReader(RawFd),
|
|
74
77
|
StartWriter {
|
|
75
78
|
fd: RawFd,
|
|
76
|
-
callback:
|
|
77
|
-
args: Py<PyTuple>,
|
|
78
|
-
context: Py<PyAny>,
|
|
79
|
-
context_needs_run: bool,
|
|
79
|
+
callback: Arc<ReadyCallback>,
|
|
80
80
|
},
|
|
81
81
|
StopWriter(RawFd),
|
|
82
82
|
StartSocketReader {
|