rsloop 0.1.48__tar.gz → 0.1.50__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.
Files changed (287) hide show
  1. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/tests.yml +1 -48
  2. rsloop-0.1.50/.github/workflows/vibeio-native.yml +109 -0
  3. {rsloop-0.1.48 → rsloop-0.1.50}/Cargo.lock +1 -1
  4. {rsloop-0.1.48 → rsloop-0.1.50}/Cargo.toml +1 -1
  5. {rsloop-0.1.48 → rsloop-0.1.50}/PKG-INFO +91 -52
  6. {rsloop-0.1.48 → rsloop-0.1.50}/README.md +90 -51
  7. {rsloop-0.1.48 → rsloop-0.1.50}/benches/README.md +36 -11
  8. {rsloop-0.1.48 → rsloop-0.1.50}/benches/compare_event_loops.py +9 -3
  9. rsloop-0.1.50/benches/full-benchmark-6cc3444.md +115 -0
  10. rsloop-0.1.50/benches/full-benchmark-9011c9f.md +69 -0
  11. rsloop-0.1.50/benches/optimization-results.md +163 -0
  12. rsloop-0.1.50/benches/optimization_matrix_ab.py +112 -0
  13. rsloop-0.1.50/benches/optimization_probes.py +297 -0
  14. rsloop-0.1.50/benches/reactor-performance.md +176 -0
  15. rsloop-0.1.50/benches/results/full-6cc3444-idle.json +1275 -0
  16. rsloop-0.1.50/benches/results/full-6cc3444-matrix.json +6321 -0
  17. rsloop-0.1.50/benches/results/full-6cc3444-micro.json +801 -0
  18. rsloop-0.1.50/benches/results/full-9011c9f-idle.json +1225 -0
  19. rsloop-0.1.50/benches/results/full-9011c9f-matrix.json +6271 -0
  20. rsloop-0.1.50/benches/results/full-9011c9f-micro.json +559 -0
  21. rsloop-0.1.50/benches/results/optimization-matrix-2026-09-07.json +2139 -0
  22. rsloop-0.1.50/benches/results/optimization-probes-2026-09-07.json +377 -0
  23. rsloop-0.1.50/benches/results/reactor-performance-2026-09-07.json +4133 -0
  24. rsloop-0.1.50/benches/results/systems-performance-2026-09-07.json +9662 -0
  25. rsloop-0.1.50/benches/systems-performance.md +206 -0
  26. {rsloop-0.1.48 → rsloop-0.1.50}/benches/workload_matrix.py +2 -1
  27. {rsloop-0.1.48 → rsloop-0.1.50}/docs/development.md +28 -6
  28. {rsloop-0.1.48 → rsloop-0.1.50}/justfile +1 -1
  29. {rsloop-0.1.48 → rsloop-0.1.50}/pyproject.toml +17 -2
  30. {rsloop-0.1.48 → rsloop-0.1.50}/qualirs.toml +11 -0
  31. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/run_python_tests.py +6 -5
  32. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/test-supported-pythons.sh +3 -1
  33. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/executor.rs +79 -4
  34. rsloop-0.1.50/src/bindings/loop_api/fast_callbacks.rs +142 -0
  35. rsloop-0.1.50/src/bindings/loop_api/sock_ops.rs +113 -0
  36. rsloop-0.1.50/src/bindings/loop_api/socket_operation.rs +356 -0
  37. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api.rs +3 -0
  38. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/mod.rs +1 -0
  39. {rsloop-0.1.48 → rsloop-0.1.50}/src/engine/callbacks.rs +20 -3
  40. {rsloop-0.1.48 → rsloop-0.1.50}/src/engine/commands.rs +0 -8
  41. {rsloop-0.1.48 → rsloop-0.1.50}/src/engine/dispatcher.rs +3 -88
  42. {rsloop-0.1.48 → rsloop-0.1.50}/src/engine/loop_core.rs +163 -16
  43. {rsloop-0.1.48 → rsloop-0.1.50}/src/engine/mod.rs +2 -0
  44. {rsloop-0.1.48/src/engine/dispatcher → rsloop-0.1.50/src/engine}/timer_entry.rs +1 -1
  45. {rsloop-0.1.48 → rsloop-0.1.50}/src/lib.rs +0 -2
  46. {rsloop-0.1.48 → rsloop-0.1.50}/src/module_init.rs +1 -0
  47. {rsloop-0.1.48 → rsloop-0.1.50}/src/platform/fd/mod.rs +1 -34
  48. {rsloop-0.1.48 → rsloop-0.1.50}/src/platform/fd/windows.rs +1 -32
  49. {rsloop-0.1.48 → rsloop-0.1.50}/src/platform/mod.rs +0 -3
  50. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/buffers.rs +91 -2
  51. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/core_protocol.rs +114 -31
  52. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/core_write.rs +124 -11
  53. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/fast.rs +3 -0
  54. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/py_transport.rs +13 -3
  55. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/write_queue.rs +8 -0
  56. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/blocking/default.rs +1 -1
  57. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/kqueue.rs +17 -2
  58. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/mio.rs +19 -9
  59. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/fs/mod.rs +4 -1
  60. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/buf.rs +7 -3
  61. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/splice.rs +2 -3
  62. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/util.rs +1 -1
  63. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/lib.rs +11 -0
  64. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/tcp/listener.rs +1 -1
  65. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/tcp/mod.rs +16 -22
  66. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/tcp/stream.rs +1 -1
  67. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/udp.rs +5 -3
  68. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/unix/mod.rs +16 -22
  69. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/accept.rs +27 -20
  70. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/accept_unix.rs +22 -16
  71. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/connect.rs +84 -12
  72. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/io_util.rs +44 -20
  73. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/open.rs +2 -2
  74. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/read.rs +58 -20
  75. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/readv.rs +34 -37
  76. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/recv.rs +1 -1
  77. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/recvfrom.rs +23 -20
  78. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/sendto.rs +1 -1
  79. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/splice.rs +13 -4
  80. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/waitpid.rs +1 -2
  81. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/writeat.rs +1 -1
  82. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/process/mod.rs +16 -3
  83. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/process/reaper.rs +15 -7
  84. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/signal/unix.rs +20 -21
  85. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/signal/windows.rs +7 -16
  86. rsloop-0.1.50/src/vibeio/test_support.rs +281 -0
  87. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/time/sleep.rs +2 -2
  88. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/time/timeout.rs +3 -6
  89. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/timer.rs +41 -34
  90. rsloop-0.1.50/tests/test_benchmark_loops.py +127 -0
  91. rsloop-0.1.50/tests/test_buffered_delivery.py +102 -0
  92. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_compat.py +170 -176
  93. rsloop-0.1.50/tests/test_fast_callbacks.py +115 -0
  94. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_free_threading.py +10 -14
  95. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_idle_benchmark.py +104 -93
  96. rsloop-0.1.50/tests/test_local_sockets.py +235 -0
  97. rsloop-0.1.50/tests/test_local_timers.py +90 -0
  98. rsloop-0.1.50/tests/test_numeric_addrinfo.py +126 -0
  99. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_run.py +119 -152
  100. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_stream_reader.py +36 -43
  101. rsloop-0.1.50/tests/test_test_runner.py +87 -0
  102. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_tls.py +22 -29
  103. rsloop-0.1.50/tests/test_write_reentrancy.py +73 -0
  104. {rsloop-0.1.48 → rsloop-0.1.50}/uv.lock +110 -42
  105. rsloop-0.1.48/src/bindings/loop_api/sock_ops.rs +0 -168
  106. rsloop-0.1.48/src/platform/windows_vibeio.rs +0 -180
  107. {rsloop-0.1.48 → rsloop-0.1.50}/.cargo/config.toml +0 -0
  108. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/anyio.yml +0 -0
  109. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/kani.yml +0 -0
  110. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/mkdocs.yml +0 -0
  111. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/update-version.yml +0 -0
  112. {rsloop-0.1.48 → rsloop-0.1.50}/.github/workflows/wheels.yml +0 -0
  113. {rsloop-0.1.48 → rsloop-0.1.50}/.gitignore +0 -0
  114. {rsloop-0.1.48 → rsloop-0.1.50}/.python-version +0 -0
  115. {rsloop-0.1.48 → rsloop-0.1.50}/.vscode/settings.json +0 -0
  116. {rsloop-0.1.48 → rsloop-0.1.50}/LICENSE +0 -0
  117. {rsloop-0.1.48 → rsloop-0.1.50}/benches/check_regression.py +0 -0
  118. {rsloop-0.1.48 → rsloop-0.1.50}/benches/idle_statistics.py +0 -0
  119. {rsloop-0.1.48 → rsloop-0.1.50}/benches/runtime.rs +0 -0
  120. {rsloop-0.1.48 → rsloop-0.1.50}/benches/timer.rs +0 -0
  121. {rsloop-0.1.48 → rsloop-0.1.50}/benches/vibeio-performance.md +0 -0
  122. {rsloop-0.1.48 → rsloop-0.1.50}/build.rs +0 -0
  123. {rsloop-0.1.48 → rsloop-0.1.50}/docs/examples.md +0 -0
  124. {rsloop-0.1.48 → rsloop-0.1.50}/docs/getting-started.md +0 -0
  125. {rsloop-0.1.48 → rsloop-0.1.50}/docs/how-it-works.md +0 -0
  126. {rsloop-0.1.48 → rsloop-0.1.50}/docs/index.md +0 -0
  127. {rsloop-0.1.48 → rsloop-0.1.50}/docs/project-structure.md +0 -0
  128. {rsloop-0.1.48 → rsloop-0.1.50}/docs/rsloop.png +0 -0
  129. {rsloop-0.1.48 → rsloop-0.1.50}/docs/rust-extensions.md +0 -0
  130. {rsloop-0.1.48 → rsloop-0.1.50}/docs/verification.md +0 -0
  131. {rsloop-0.1.48 → rsloop-0.1.50}/docs/vibeio-cleanup.md +0 -0
  132. {rsloop-0.1.48 → rsloop-0.1.50}/docs/vibeio-findings.md +0 -0
  133. {rsloop-0.1.48 → rsloop-0.1.50}/examples/01_basics.py +0 -0
  134. {rsloop-0.1.48 → rsloop-0.1.50}/examples/02_fd_and_sockets.py +0 -0
  135. {rsloop-0.1.48 → rsloop-0.1.50}/examples/03_streams.py +0 -0
  136. {rsloop-0.1.48 → rsloop-0.1.50}/examples/04_unix_and_accepted_socket.py +0 -0
  137. {rsloop-0.1.48 → rsloop-0.1.50}/examples/05_pipes_signals_subprocesses.py +0 -0
  138. {rsloop-0.1.48 → rsloop-0.1.50}/examples/README.md +0 -0
  139. {rsloop-0.1.48 → rsloop-0.1.50}/examples/fastapi_service.py +0 -0
  140. {rsloop-0.1.48 → rsloop-0.1.50}/examples/picows_server.py +0 -0
  141. {rsloop-0.1.48 → rsloop-0.1.50}/examples/picows_test.py +0 -0
  142. {rsloop-0.1.48 → rsloop-0.1.50}/examples/wsbench_websockets.py +0 -0
  143. {rsloop-0.1.48 → rsloop-0.1.50}/mkdocs.yml +0 -0
  144. {rsloop-0.1.48 → rsloop-0.1.50}/pyrightconfig.json +0 -0
  145. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/__init__.py +0 -0
  146. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/__init__.pyi +0 -0
  147. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/_bootstrap.py +0 -0
  148. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/_loop.pyi +0 -0
  149. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/_loop_compat.py +0 -0
  150. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/_profile.py +0 -0
  151. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/_run.py +0 -0
  152. {rsloop-0.1.48 → rsloop-0.1.50}/python/rsloop/py.typed +0 -0
  153. {rsloop-0.1.48 → rsloop-0.1.50}/rust-toolchain.toml +0 -0
  154. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/build-pgo-wheels.sh +0 -0
  155. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/build-wheels.sh +0 -0
  156. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/generate_test_tls_certs.py +0 -0
  157. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/prepare_anyio_rsloop_tests.py +0 -0
  158. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/python-versions.sh +0 -0
  159. {rsloop-0.1.48 → rsloop-0.1.50}/scripts/run_rust_tests.py +0 -0
  160. {rsloop-0.1.48 → rsloop-0.1.50}/src/async_event.rs +0 -0
  161. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/asyncgens.rs +0 -0
  162. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/asyncio_cache.rs +0 -0
  163. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/connections.rs +0 -0
  164. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/ffi_helpers.rs +0 -0
  165. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/lifecycle.rs +0 -0
  166. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/methods.rs +0 -0
  167. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/pipes.rs +0 -0
  168. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/pre_exec.rs +0 -0
  169. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/process_handles.rs +0 -0
  170. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/process_spawn.rs +0 -0
  171. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/process_stdio.rs +0 -0
  172. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/servers.rs +0 -0
  173. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/signals.rs +0 -0
  174. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/socket_connect.rs +0 -0
  175. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/sockets.rs +0 -0
  176. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/spawn_env.rs +0 -0
  177. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/tasks.rs +0 -0
  178. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/tls_params.rs +0 -0
  179. {rsloop-0.1.48 → rsloop-0.1.50}/src/bindings/loop_api/watchers.rs +0 -0
  180. {rsloop-0.1.48 → rsloop-0.1.50}/src/blocking.rs +0 -0
  181. {rsloop-0.1.48 → rsloop-0.1.50}/src/build_metadata.rs +0 -0
  182. {rsloop-0.1.48 → rsloop-0.1.50}/src/context.rs +0 -0
  183. {rsloop-0.1.48 → rsloop-0.1.50}/src/errors.rs +0 -0
  184. {rsloop-0.1.48 → rsloop-0.1.50}/src/profiler.rs +0 -0
  185. {rsloop-0.1.48 → rsloop-0.1.50}/src/python_names.rs +0 -0
  186. {rsloop-0.1.48 → rsloop-0.1.50}/src/rust_async.rs +0 -0
  187. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/mod.rs +0 -0
  188. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/core_events.rs +0 -0
  189. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/core_protocol.rs +0 -0
  190. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/core_state.rs +0 -0
  191. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/mod.rs +0 -0
  192. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/params.rs +0 -0
  193. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/py_pipes.rs +0 -0
  194. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/py_transport.rs +0 -0
  195. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/spawn.rs +0 -0
  196. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/process/worker.rs +0 -0
  197. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/accept.rs +0 -0
  198. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/builder.rs +0 -0
  199. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/connect.rs +0 -0
  200. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/core_events.rs +0 -0
  201. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/core_state.rs +0 -0
  202. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/io_targets.rs +0 -0
  203. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/mod.rs +0 -0
  204. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/pipe_transport.rs +0 -0
  205. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/platform.rs +0 -0
  206. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/poll.rs +0 -0
  207. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/protocol.rs +0 -0
  208. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/py_server.rs +0 -0
  209. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/reader.rs +0 -0
  210. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/reader_task.rs +0 -0
  211. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/server.rs +0 -0
  212. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/server_core.rs +0 -0
  213. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/server_types.rs +0 -0
  214. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/socket_transport.rs +0 -0
  215. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/stats.rs +0 -0
  216. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/tls_session.rs +0 -0
  217. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/tls_transport.rs +0 -0
  218. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/tuning.rs +0 -0
  219. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/worker.rs +0 -0
  220. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/stream/writer.rs +0 -0
  221. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/tls/material.rs +0 -0
  222. {rsloop-0.1.48 → rsloop-0.1.50}/src/transport/tls/mod.rs +0 -0
  223. {rsloop-0.1.48 → rsloop-0.1.50}/src/verification.rs +0 -0
  224. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/UPSTREAM.md +0 -0
  225. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/blocking/mod.rs +0 -0
  226. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/builder.rs +0 -0
  227. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/iocp.rs +0 -0
  228. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/mock.rs +0 -0
  229. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/mod.rs +0 -0
  230. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/driver/uring.rs +0 -0
  231. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/executor.rs +0 -0
  232. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/fd_inner.rs +0 -0
  233. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/fs/file.rs +0 -0
  234. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/fs/metadata.rs +0 -0
  235. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/fs/open_options.rs +0 -0
  236. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/mod.rs +0 -0
  237. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/pipe.rs +0 -0
  238. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/io/stdio.rs +0 -0
  239. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/mod.rs +0 -0
  240. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/unix/listener.rs +0 -0
  241. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/net/unix/stream.rs +0 -0
  242. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/fsync.rs +0 -0
  243. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/hard_link.rs +0 -0
  244. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/mkdir.rs +0 -0
  245. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/mod.rs +0 -0
  246. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/readat.rs +0 -0
  247. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/readiness.rs +0 -0
  248. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/rename.rs +0 -0
  249. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/send.rs +0 -0
  250. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/socket_addr.rs +0 -0
  251. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/statx.rs +0 -0
  252. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/symlink.rs +0 -0
  253. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/unlink.rs +0 -0
  254. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/write.rs +0 -0
  255. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/op/writev.rs +0 -0
  256. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/signal/mod.rs +0 -0
  257. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/task.rs +0 -0
  258. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/time/interval.rs +0 -0
  259. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/time/mod.rs +0 -0
  260. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/util/async_wrap.rs +0 -0
  261. {rsloop-0.1.48 → rsloop-0.1.50}/src/vibeio/util/mod.rs +0 -0
  262. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/_smoke.py +0 -0
  263. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/aiohttp_test.py +0 -0
  264. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/anyio_test.py +0 -0
  265. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/beanie_test.py +0 -0
  266. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/daphne_test.py +0 -0
  267. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/django_asgi_test.py +0 -0
  268. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/django_orm_test.py +0 -0
  269. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/edgy_test.py +0 -0
  270. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/falcon_test.py +0 -0
  271. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/fastapi_test.py +0 -0
  272. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/faststream_test.py +0 -0
  273. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/hypercorn_test.py +0 -0
  274. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/litestar_granian_test.py +0 -0
  275. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/litestar_test.py +0 -0
  276. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/mangum_test.py +0 -0
  277. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/ormar_test.py +0 -0
  278. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/piccolo_test.py +0 -0
  279. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/quart_test.py +0 -0
  280. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/sanic_test.py +0 -0
  281. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/sqlalchemy_test.py +0 -0
  282. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/sqlmodel_test.py +0 -0
  283. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/starlette_test.py +0 -0
  284. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/tortoise_orm_test.py +0 -0
  285. {rsloop-0.1.48 → rsloop-0.1.50}/tests/packages/uvicorn_test.py +0 -0
  286. {rsloop-0.1.48 → rsloop-0.1.50}/tests/proptest-regressions/transport/stream/fast.txt +0 -0
  287. {rsloop-0.1.48 → rsloop-0.1.50}/tests/test_public_api.py +0 -0
@@ -62,54 +62,7 @@ jobs:
62
62
  run: python3 scripts/run_rust_tests.py ${{ matrix.test_args }}
63
63
 
64
64
  vibeio-native:
65
- name: embedded runtime (${{ matrix.os }})
66
- runs-on: ${{ matrix.os }}
67
- timeout-minutes: 15
68
- strategy:
69
- fail-fast: false
70
- matrix:
71
- os: [ubuntu-latest, windows-latest, macos-latest]
72
- steps:
73
- - name: Check out repository
74
- uses: actions/checkout@v6
75
- with:
76
- persist-credentials: false
77
-
78
- - name: Set up Rust
79
- uses: dtolnay/rust-toolchain@stable
80
- with:
81
- toolchain: "1.98.1"
82
- components: clippy, rustfmt
83
-
84
- - name: Cache runtime harness build
85
- uses: Swatinem/rust-cache@v2
86
- with:
87
- workspaces: tools/vibeio-check
88
-
89
- - name: Check embedded runtime formatting
90
- run: cargo fmt --manifest-path tools/vibeio-check/Cargo.toml --all -- --check
91
-
92
- - name: Check embedded runtime documentation
93
- env:
94
- RUSTDOCFLAGS: -D warnings
95
- run: cargo doc --manifest-path tools/vibeio-check/Cargo.toml --all-features --no-deps --locked
96
-
97
- - name: Lint embedded runtime on the native target
98
- run: cargo clippy --manifest-path tools/vibeio-check/Cargo.toml --all-targets --all-features --locked -- -D warnings
99
-
100
- - name: Check isolated embedded runtime features
101
- shell: bash
102
- run: |
103
- for feature in '' fs process signal pipe stdio splice blocking-default; do
104
- cargo clippy --manifest-path tools/vibeio-check/Cargo.toml --all-targets --no-default-features --features "$feature" --locked -- -D warnings
105
- cargo test --manifest-path tools/vibeio-check/Cargo.toml --no-default-features --features "$feature" --locked
106
- done
107
-
108
- - name: Run default-feature embedded runtime tests
109
- run: cargo test --manifest-path tools/vibeio-check/Cargo.toml --locked
110
-
111
- - name: Run all-feature embedded runtime tests
112
- run: cargo test --manifest-path tools/vibeio-check/Cargo.toml --all-features --locked
65
+ uses: ./.github/workflows/vibeio-native.yml
113
66
 
114
67
  clippy:
115
68
  name: clippy (risk-focused)
@@ -0,0 +1,109 @@
1
+ name: Vibeio native tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ repetitions:
7
+ description: "All-feature test repetitions per native platform"
8
+ type: choice
9
+ default: "3"
10
+ options: ["1", "3", "10"]
11
+ isolated_features:
12
+ description: "Also lint and test each feature independently"
13
+ type: boolean
14
+ default: true
15
+ workflow_call:
16
+ inputs:
17
+ repetitions:
18
+ type: string
19
+ default: "1"
20
+ isolated_features:
21
+ type: boolean
22
+ default: true
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ jobs:
28
+ native:
29
+ name: vibeio (${{ matrix.os }})
30
+ runs-on: ${{ matrix.os }}
31
+ timeout-minutes: 30
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ os: [ubuntu-latest, windows-latest, macos-latest]
36
+ defaults:
37
+ run:
38
+ shell: bash
39
+ env:
40
+ RUST_BACKTRACE: "1"
41
+ CARGO_TERM_COLOR: never
42
+ REPETITIONS: ${{ inputs.repetitions }}
43
+ steps:
44
+ - name: Check out repository
45
+ uses: actions/checkout@v6
46
+ with:
47
+ persist-credentials: false
48
+
49
+ - name: Set up Rust
50
+ uses: dtolnay/rust-toolchain@stable
51
+ with:
52
+ toolchain: "1.98.1"
53
+ components: clippy, rustfmt
54
+
55
+ - name: Cache runtime harness build
56
+ uses: Swatinem/rust-cache@v2
57
+ with:
58
+ workspaces: tools/vibeio-check
59
+
60
+ - name: Record native environment
61
+ run: |
62
+ mkdir -p target/vibeio-native-logs
63
+ case "$REPETITIONS" in
64
+ 1|3|10) ;;
65
+ *) echo "Unsupported repetition count: $REPETITIONS"; exit 1 ;;
66
+ esac
67
+ {
68
+ git rev-parse HEAD
69
+ rustc -Vv
70
+ cargo -V
71
+ uname -a
72
+ } 2>&1 | tee target/vibeio-native-logs/environment.log
73
+
74
+ - name: Check formatting and documentation
75
+ env:
76
+ RUSTDOCFLAGS: -D warnings
77
+ run: |
78
+ cargo fmt --manifest-path tools/vibeio-check/Cargo.toml --all -- --check
79
+ cargo doc --manifest-path tools/vibeio-check/Cargo.toml --all-features --no-deps --locked 2>&1 | tee target/vibeio-native-logs/documentation.log
80
+
81
+ - name: Lint native all-feature build
82
+ run: cargo clippy --manifest-path tools/vibeio-check/Cargo.toml --all-targets --all-features --locked -- -D warnings 2>&1 | tee target/vibeio-native-logs/clippy.log
83
+
84
+ - name: Inventory native tests
85
+ run: cargo test --manifest-path tools/vibeio-check/Cargo.toml --all-features --locked -- --list 2>&1 | tee target/vibeio-native-logs/test-inventory.log
86
+
87
+ - name: Run native all-feature tests repeatedly
88
+ run: |
89
+ for ((iteration=1; iteration<=REPETITIONS; iteration++)); do
90
+ cargo test --manifest-path tools/vibeio-check/Cargo.toml --all-features --locked -- --nocapture 2>&1 | tee "target/vibeio-native-logs/all-features-${iteration}.log"
91
+ done
92
+
93
+ - name: Check isolated features
94
+ if: ${{ inputs.isolated_features }}
95
+ run: |
96
+ for feature in '' fs process signal pipe stdio splice blocking-default; do
97
+ label="${feature:-default}"
98
+ cargo clippy --manifest-path tools/vibeio-check/Cargo.toml --all-targets --no-default-features --features "$feature" --locked -- -D warnings 2>&1 | tee "target/vibeio-native-logs/clippy-${label}.log"
99
+ cargo test --manifest-path tools/vibeio-check/Cargo.toml --no-default-features --features "$feature" --locked -- --nocapture 2>&1 | tee "target/vibeio-native-logs/tests-${label}.log"
100
+ done
101
+
102
+ - name: Upload native test evidence
103
+ if: always()
104
+ uses: actions/upload-artifact@v4
105
+ with:
106
+ name: vibeio-native-${{ matrix.os }}-${{ github.run_attempt }}
107
+ path: target/vibeio-native-logs/
108
+ if-no-files-found: warn
109
+ retention-days: 14
@@ -935,7 +935,7 @@ dependencies = [
935
935
 
936
936
  [[package]]
937
937
  name = "rsloop"
938
- version = "0.1.48"
938
+ version = "0.1.50"
939
939
  dependencies = [
940
940
  "async-channel 2.5.0",
941
941
  "async-io",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rsloop"
3
- version = "0.1.48"
3
+ version = "0.1.50"
4
4
  edition = "2024"
5
5
  description = "An event loop for asyncio written in Rust"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rsloop
3
- Version: 0.1.48
3
+ Version: 0.1.50
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -496,10 +496,36 @@ The repository also includes:
496
496
 
497
497
  ```bash
498
498
  uv run --with maturin maturin develop --release
499
- uv run --with uvloop python benches/compare_event_loops.py
499
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py \
500
+ --loops asyncio,uvloop,zuvloop,rsloop --repeat 7 --warmups 2
500
501
  ```
501
502
 
502
- An example output from that script on macOS (arm64) with CPython 3.14:
503
+ ### Four-loop comparison on Linux
504
+
505
+ Measured on September 7, 2026 at commit `6cc3444` on an Intel Core i9-9900K,
506
+ Linux 7.0.0-31-generic (x86_64), and CPython 3.14.7, with rsloop 0.1.49 built
507
+ in release mode, uvloop 0.22.1, and zuvloop 0.0.14. Each entry is the median
508
+ of seven measured runs after two warmups, with each run in a fresh subprocess.
509
+ Times are milliseconds; lower is better.
510
+
511
+ | Workload | asyncio | uvloop | zuvloop | rsloop |
512
+ |---|---:|---:|---:|---:|
513
+ | 200,000 callbacks | 112.49 | 51.57 | **36.23** | 48.31 |
514
+ | 50,000 tasks | 149.49 | 93.55 | **81.47** | 89.54 |
515
+ | 5,000 TCP roundtrips | 150.87 | 126.20 | 109.64 | **84.60** |
516
+
517
+ The TCP workload uses 1,024-byte payloads and rsloop's native fast streams;
518
+ the other loops use stdlib asyncio streams. Use `--no-rsloop-fast-streams`
519
+ to compare all loops through the stdlib streams layer. Zuvloop led callbacks
520
+ and tasks in this run, while rsloop led TCP roundtrips. These are local
521
+ microbenchmarks, not isolated-lab measurements or general application performance
522
+ claims; do not compare them directly with the historical macOS results below.
523
+ See the [full report and recorded results](./benches/full-benchmark-6cc3444.md)
524
+ for the exact commands, build details, and process-run samples.
525
+
526
+ ### Historical macOS comparison
527
+
528
+ An earlier example output from the script on macOS (arm64) with CPython 3.14:
503
529
 
504
530
  ```
505
531
  callbacks (200,000 ops)
@@ -521,55 +547,59 @@ uvloop 0.133182 0.127404 37,543 31.5 MiB 1.46x
521
547
  asyncio 0.302337 0.299813 16,538 29.6 MiB 3.32x 232.5%
522
548
  ```
523
549
 
550
+ ### Sustained network workloads
551
+
524
552
  The production-shaped workload matrix exercises HTTP, WebSocket libraries,
525
553
  TLS, mixed message sizes, backpressure, and connection lifecycle behavior:
526
554
 
527
555
  ```bash
528
- uv run --with uvloop python benches/workload_matrix.py \
529
- --loops rsloop,uvloop \
556
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
557
+ --loops rsloop,uvloop,zuvloop \
530
558
  --sustained \
531
- --json-output target/matrix-opt-final.json
559
+ --scenarios http_keepalive,tls_http,websocket_messages,websocket_tls,websockets_messages,websockets_tls,aiohttp_websocket_messages,aiohttp_websocket_tls,starlette_websocket_messages,starlette_websocket_tls,mixed_streams,bulk_transfer \
560
+ --json-output target/matrix-zuvloop.json
532
561
  ```
533
562
 
534
- Measured on September 6, 2026 with an Intel Core i9-9900K, Linux
535
- 7.0.0-31-generic (x86_64), CPython 3.14.0, rsloop 0.1.47 (release build), and
536
- uvloop 0.22.1. Each row reports the median of seven measured runs after two
537
- warmups, using 16 concurrent connections and 500 requests per
538
- connection. Throughput is traffic-only operations per second, except for
563
+ Measured on September 7, 2026 with an Intel Core i9-9900K, Linux
564
+ 7.0.0-31-generic (x86_64), CPython 3.14.7, rsloop 0.1.49 (release build,
565
+ commit `6cc3444`), uvloop 0.22.1, and zuvloop 0.0.14. Each row reports the
566
+ median of seven measured runs after two warmups, using 16 concurrent connections.
567
+ Request/response workloads send 500 requests per connection; bulk transfer
568
+ sends 2 MiB per connection in 64 KiB chunks. Throughput is traffic-only
569
+ operations per second, except for
539
570
  `bulk_transfer`, which reports traffic MiB/s. The p95 columns are the medians
540
- of each run's p95 latency; the difference is `(rsloop / uvloop - 1) × 100%`.
571
+ of each run's p95 latency. Higher throughput and lower latency are better.
572
+ Each loop/scenario pair runs in its own subprocess, with warmups and measured
573
+ runs sharing that process. Loops run sequentially in the order shown.
541
574
 
542
575
  WebSocket library versions were websockets 17.0.1, aiohttp 3.14.3, Starlette
543
576
  1.6.0, and uvicorn 0.52.3. The run used unrestricted CPU affinity, with other
544
577
  host services running but no concurrent builds or tests. These measurements
545
578
  are from a different host than the macOS microbenchmark example above.
546
579
 
547
- | Scenario | rsloop | uvloop | rsloop difference | rsloop p95 | uvloop p95 |
548
- | --- | ---: | ---: | ---: | ---: | ---: |
549
- | HTTP keep-alive | 51,561 | 51,410 | +0.3% | 0.354 ms | 0.351 ms |
550
- | TLS HTTP | 72,465 | 25,493 | +184.3% | 0.241 ms | 0.668 ms |
551
- | Raw WebSocket | 5,197 | 5,296 | -1.9% | 5.023 ms | 3.504 ms |
552
- | Raw WebSocket over TLS | 5,395 | 4,824 | +11.8% | 3.945 ms | 3.870 ms |
553
- | `websockets` | 22,756 | 24,780 | -8.2% | 0.830 ms | 0.690 ms |
554
- | `websockets` over TLS | 26,044 | 15,081 | +72.7% | 0.672 ms | 1.109 ms |
555
- | aiohttp WebSocket | 29,879 | 33,895 | -11.8% | 0.656 ms | 0.511 ms |
556
- | aiohttp WebSocket over TLS | 32,983 | 19,063 | +73.0% | 0.526 ms | 0.890 ms |
557
- | Starlette WebSocket | 18,524 | 20,325 | -8.9% | 0.997 ms | 0.832 ms |
558
- | Starlette WebSocket over TLS | 18,058 | 13,555 | +33.2% | 0.942 ms | 1.257 ms |
559
- | Mixed streams | 42,797 | 34,642 | +23.5% | 0.484 ms | 0.521 ms |
560
- | Bulk transfer (MiB/s) | 1,993.5 | 1,223.7 | +62.9% | 14.768 ms | 26.061 ms |
580
+ | Scenario | rsloop | uvloop | zuvloop | rsloop p95 | uvloop p95 | zuvloop p95 |
581
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
582
+ | HTTP keep-alive | 54,103 | 51,649 | 57,764 | 0.336 ms | 0.346 ms | 0.292 ms |
583
+ | TLS HTTP | 72,496 | 26,226 | 23,408 | 0.241 ms | 0.646 ms | 0.717 ms |
584
+ | Raw WebSocket | 4,870 | 4,890 | 4,898 | 4.454 ms | 3.874 ms | 3.357 ms |
585
+ | Raw WebSocket over TLS | 4,933 | 4,455 | 4,397 | 4.355 ms | 4.194 ms | 3.800 ms |
586
+ | `websockets` | 24,667 | 25,800 | 27,409 | 0.736 ms | 0.631 ms | 0.594 ms |
587
+ | `websockets` over TLS | 26,781 | 14,872 | 14,555 | 0.644 ms | 1.135 ms | 1.167 ms |
588
+ | aiohttp WebSocket | 31,636 | 32,765 | 35,425 | 0.605 ms | 0.535 ms | 0.484 ms |
589
+ | aiohttp WebSocket over TLS | 34,165 | 19,110 | 18,400 | 0.504 ms | 0.882 ms | 0.909 ms |
590
+ | Starlette WebSocket | 18,785 | 20,407 | 21,743 | 0.981 ms | 0.829 ms | 0.787 ms |
591
+ | Starlette WebSocket over TLS | 18,509 | 13,104 | 13,038 | 0.948 ms | 1.278 ms | 1.291 ms |
592
+ | Mixed streams | 43,795 | 34,196 | 37,116 | 0.463 ms | 0.523 ms | 0.473 ms |
593
+ | Bulk transfer (MiB/s) | 2,009.9 | 1,264.9 | 1,313.3 | 15.058 ms | 25.219 ms | 24.317 ms |
561
594
 
562
595
  The former single-burst idle-activation row has been retired: its traffic
563
596
  phase lasted only a few milliseconds and produced unstable throughput rankings.
564
597
  Idle activation now has a separate, versioned latency benchmark described below.
565
- The other measurements above are unchanged. HTTP's 0.3% difference is too small
566
- to call a win.
567
-
568
- Compared with the same sustained workload on the pre-optimization build,
569
- plain-text `websockets`, aiohttp, and Starlette throughput improved by 10.2%,
570
- 19.4%, and 22.3%, respectively. They still trail uvloop. The historical regression
571
- gate also flagged HTTP tail latency and legacy idle activation; this is not an
572
- across-the-board performance win. See the
598
+ In this run, zuvloop had the highest plaintext HTTP and WebSocket throughput,
599
+ while rsloop led TLS throughput, mixed streams, and bulk transfer. Throughput
600
+ and tail latency do not always agree: rsloop's raw WebSocket p95 was higher
601
+ than both alternatives, including over TLS. These results are not an
602
+ across-the-board performance win or a before/after regression measurement. See the
573
603
  [benchmark documentation](./benches/README.md) for workload definitions and
574
604
  reproduction commands.
575
605
 
@@ -582,41 +612,50 @@ so has more to lose when cores are contended.
582
612
  ### Idle activation latency
583
613
 
584
614
  ```bash
585
- .venv/bin/python benches/workload_matrix.py \
586
- --loops rsloop,uvloop --scenarios idle_connections --repeat 8 \
615
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
616
+ --loops rsloop,uvloop,zuvloop --scenarios idle_connections --repeat 9 \
587
617
  --idle-cycles 100 --idle-warmup-cycles 5 --idle-seconds 0.2 \
588
618
  --json-output target/idle-v2-paired.json
589
619
  ```
590
620
 
591
621
  Idle v2 reuses 200 established connections across repeated idle/wakeup cycles.
592
622
  It measures all replies from one shared activation timestamp, including task
593
- scheduling delay, and reports first/50%/95%/all-reply latency. Eight fresh-process
594
- blocks alternate loop order; confidence intervals resample whole paired runs,
623
+ scheduling delay, and reports first/50%/95%/all-reply latency. Nine fresh-process
624
+ blocks rotate loop order so each loop runs first three times; confidence intervals resample whole paired runs,
595
625
  not individual connections. Results are classified as improved, regressed, or
596
626
  inconclusive using a 5% practical threshold and an approximate 95% confidence
597
- interval. The command takes about six minutes; use `--idle-cycles 3
627
+ interval. The command takes about ten minutes; use `--idle-cycles 3
598
628
  --idle-warmup-cycles 1 --idle-seconds 0.01 --repeat 1` for a smoke test only.
599
629
 
600
630
  The new measurements cannot be compared with the retired ops/s row. See
601
631
  [benchmark methodology and regression handling](./benches/README.md#idle-activation-v2)
602
632
  for timing definitions, host controls, raw distributions, and sample requirements.
603
633
 
604
- Validation on the Linux/i9-9900K host above collected 800 measured cycles per
605
- loop in 16 distinct processes, with unrestricted affinity and no concurrent
606
- builds or test runs. These are medians across runs of each run's median cycle
607
- milestone, in milliseconds (lower is better):
634
+ Measured on September 7, 2026 at commit `6cc3444` on the Linux/i9-9900K
635
+ host above with CPython 3.14.7, rsloop 0.1.49 (release), uvloop 0.22.1,
636
+ and zuvloop 0.0.14. The run collected 900 measured cycles per loop in 27
637
+ distinct processes, with unrestricted affinity and no concurrent builds or
638
+ test runs. These are medians across runs of each run's median cycle milestone,
639
+ in milliseconds (lower is better):
608
640
 
609
641
  | Loop | First reply | 50% replied | 95% replied | All replied |
610
642
  | --- | ---: | ---: | ---: | ---: |
611
- | rsloop | 17.510 | 17.893 | 18.169 | 18.299 |
612
- | uvloop | 18.072 | 18.584 | 19.029 | 19.076 |
613
-
614
- The preselected paired comparison is **inconclusive**: the geometric mean
615
- run-level p95 latency change for rsloop versus uvloop is +9.6%, with a 95%
616
- bootstrap interval of [-4.8%, +32.2%]. This uses paired run ratios, not the
617
- ratio of the table's medians. Individual cycle-p95 latencies still form fast
618
- and slow clusters (rsloop 3.423–28.634 ms; uvloop 3.708–24.329 ms). The new
619
- benchmark exposes that uncertainty rather than declaring a throughput winner.
643
+ | rsloop | 17.221 | 17.579 | 17.857 | 17.991 |
644
+ | uvloop | 18.193 | 18.722 | 19.174 | 19.221 |
645
+ | zuvloop | 16.441 | 16.869 | 17.248 | 17.288 |
646
+
647
+ Comparisons against uvloop use geometric mean paired process-run ratios,
648
+ not ratios of the table medians:
649
+
650
+ - rsloop: -23.5% p95 latency, approximate 95% interval [-48.6%, +5.9%]; **inconclusive** at the 5% threshold.
651
+ - zuvloop: -15.2% p95 latency, approximate 95% interval [-31.0%, +4.5%]; **inconclusive** at the 5% threshold.
652
+
653
+ Individual cycle-p95 latencies span 3.374–28.184 ms for rsloop,
654
+ 3.736–24.228 ms for uvloop, and 3.454–26.163 ms for zuvloop.
655
+ Median ordering alone does not establish a latency win.
656
+
657
+ The [full report](./benches/full-benchmark-6cc3444.md) links the recorded
658
+ measurements and documents the settings used for all three benchmark suites.
620
659
 
621
660
  See [`benches/README.md`](./benches/README.md) for workload details and
622
661
  extra flags, and [`examples/README.md`](./examples/README.md) for the FastAPI
@@ -464,10 +464,36 @@ The repository also includes:
464
464
 
465
465
  ```bash
466
466
  uv run --with maturin maturin develop --release
467
- uv run --with uvloop python benches/compare_event_loops.py
467
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py \
468
+ --loops asyncio,uvloop,zuvloop,rsloop --repeat 7 --warmups 2
468
469
  ```
469
470
 
470
- An example output from that script on macOS (arm64) with CPython 3.14:
471
+ ### Four-loop comparison on Linux
472
+
473
+ Measured on September 7, 2026 at commit `6cc3444` on an Intel Core i9-9900K,
474
+ Linux 7.0.0-31-generic (x86_64), and CPython 3.14.7, with rsloop 0.1.49 built
475
+ in release mode, uvloop 0.22.1, and zuvloop 0.0.14. Each entry is the median
476
+ of seven measured runs after two warmups, with each run in a fresh subprocess.
477
+ Times are milliseconds; lower is better.
478
+
479
+ | Workload | asyncio | uvloop | zuvloop | rsloop |
480
+ |---|---:|---:|---:|---:|
481
+ | 200,000 callbacks | 112.49 | 51.57 | **36.23** | 48.31 |
482
+ | 50,000 tasks | 149.49 | 93.55 | **81.47** | 89.54 |
483
+ | 5,000 TCP roundtrips | 150.87 | 126.20 | 109.64 | **84.60** |
484
+
485
+ The TCP workload uses 1,024-byte payloads and rsloop's native fast streams;
486
+ the other loops use stdlib asyncio streams. Use `--no-rsloop-fast-streams`
487
+ to compare all loops through the stdlib streams layer. Zuvloop led callbacks
488
+ and tasks in this run, while rsloop led TCP roundtrips. These are local
489
+ microbenchmarks, not isolated-lab measurements or general application performance
490
+ claims; do not compare them directly with the historical macOS results below.
491
+ See the [full report and recorded results](./benches/full-benchmark-6cc3444.md)
492
+ for the exact commands, build details, and process-run samples.
493
+
494
+ ### Historical macOS comparison
495
+
496
+ An earlier example output from the script on macOS (arm64) with CPython 3.14:
471
497
 
472
498
  ```
473
499
  callbacks (200,000 ops)
@@ -489,55 +515,59 @@ uvloop 0.133182 0.127404 37,543 31.5 MiB 1.46x
489
515
  asyncio 0.302337 0.299813 16,538 29.6 MiB 3.32x 232.5%
490
516
  ```
491
517
 
518
+ ### Sustained network workloads
519
+
492
520
  The production-shaped workload matrix exercises HTTP, WebSocket libraries,
493
521
  TLS, mixed message sizes, backpressure, and connection lifecycle behavior:
494
522
 
495
523
  ```bash
496
- uv run --with uvloop python benches/workload_matrix.py \
497
- --loops rsloop,uvloop \
524
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
525
+ --loops rsloop,uvloop,zuvloop \
498
526
  --sustained \
499
- --json-output target/matrix-opt-final.json
527
+ --scenarios http_keepalive,tls_http,websocket_messages,websocket_tls,websockets_messages,websockets_tls,aiohttp_websocket_messages,aiohttp_websocket_tls,starlette_websocket_messages,starlette_websocket_tls,mixed_streams,bulk_transfer \
528
+ --json-output target/matrix-zuvloop.json
500
529
  ```
501
530
 
502
- Measured on September 6, 2026 with an Intel Core i9-9900K, Linux
503
- 7.0.0-31-generic (x86_64), CPython 3.14.0, rsloop 0.1.47 (release build), and
504
- uvloop 0.22.1. Each row reports the median of seven measured runs after two
505
- warmups, using 16 concurrent connections and 500 requests per
506
- connection. Throughput is traffic-only operations per second, except for
531
+ Measured on September 7, 2026 with an Intel Core i9-9900K, Linux
532
+ 7.0.0-31-generic (x86_64), CPython 3.14.7, rsloop 0.1.49 (release build,
533
+ commit `6cc3444`), uvloop 0.22.1, and zuvloop 0.0.14. Each row reports the
534
+ median of seven measured runs after two warmups, using 16 concurrent connections.
535
+ Request/response workloads send 500 requests per connection; bulk transfer
536
+ sends 2 MiB per connection in 64 KiB chunks. Throughput is traffic-only
537
+ operations per second, except for
507
538
  `bulk_transfer`, which reports traffic MiB/s. The p95 columns are the medians
508
- of each run's p95 latency; the difference is `(rsloop / uvloop - 1) × 100%`.
539
+ of each run's p95 latency. Higher throughput and lower latency are better.
540
+ Each loop/scenario pair runs in its own subprocess, with warmups and measured
541
+ runs sharing that process. Loops run sequentially in the order shown.
509
542
 
510
543
  WebSocket library versions were websockets 17.0.1, aiohttp 3.14.3, Starlette
511
544
  1.6.0, and uvicorn 0.52.3. The run used unrestricted CPU affinity, with other
512
545
  host services running but no concurrent builds or tests. These measurements
513
546
  are from a different host than the macOS microbenchmark example above.
514
547
 
515
- | Scenario | rsloop | uvloop | rsloop difference | rsloop p95 | uvloop p95 |
516
- | --- | ---: | ---: | ---: | ---: | ---: |
517
- | HTTP keep-alive | 51,561 | 51,410 | +0.3% | 0.354 ms | 0.351 ms |
518
- | TLS HTTP | 72,465 | 25,493 | +184.3% | 0.241 ms | 0.668 ms |
519
- | Raw WebSocket | 5,197 | 5,296 | -1.9% | 5.023 ms | 3.504 ms |
520
- | Raw WebSocket over TLS | 5,395 | 4,824 | +11.8% | 3.945 ms | 3.870 ms |
521
- | `websockets` | 22,756 | 24,780 | -8.2% | 0.830 ms | 0.690 ms |
522
- | `websockets` over TLS | 26,044 | 15,081 | +72.7% | 0.672 ms | 1.109 ms |
523
- | aiohttp WebSocket | 29,879 | 33,895 | -11.8% | 0.656 ms | 0.511 ms |
524
- | aiohttp WebSocket over TLS | 32,983 | 19,063 | +73.0% | 0.526 ms | 0.890 ms |
525
- | Starlette WebSocket | 18,524 | 20,325 | -8.9% | 0.997 ms | 0.832 ms |
526
- | Starlette WebSocket over TLS | 18,058 | 13,555 | +33.2% | 0.942 ms | 1.257 ms |
527
- | Mixed streams | 42,797 | 34,642 | +23.5% | 0.484 ms | 0.521 ms |
528
- | Bulk transfer (MiB/s) | 1,993.5 | 1,223.7 | +62.9% | 14.768 ms | 26.061 ms |
548
+ | Scenario | rsloop | uvloop | zuvloop | rsloop p95 | uvloop p95 | zuvloop p95 |
549
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
550
+ | HTTP keep-alive | 54,103 | 51,649 | 57,764 | 0.336 ms | 0.346 ms | 0.292 ms |
551
+ | TLS HTTP | 72,496 | 26,226 | 23,408 | 0.241 ms | 0.646 ms | 0.717 ms |
552
+ | Raw WebSocket | 4,870 | 4,890 | 4,898 | 4.454 ms | 3.874 ms | 3.357 ms |
553
+ | Raw WebSocket over TLS | 4,933 | 4,455 | 4,397 | 4.355 ms | 4.194 ms | 3.800 ms |
554
+ | `websockets` | 24,667 | 25,800 | 27,409 | 0.736 ms | 0.631 ms | 0.594 ms |
555
+ | `websockets` over TLS | 26,781 | 14,872 | 14,555 | 0.644 ms | 1.135 ms | 1.167 ms |
556
+ | aiohttp WebSocket | 31,636 | 32,765 | 35,425 | 0.605 ms | 0.535 ms | 0.484 ms |
557
+ | aiohttp WebSocket over TLS | 34,165 | 19,110 | 18,400 | 0.504 ms | 0.882 ms | 0.909 ms |
558
+ | Starlette WebSocket | 18,785 | 20,407 | 21,743 | 0.981 ms | 0.829 ms | 0.787 ms |
559
+ | Starlette WebSocket over TLS | 18,509 | 13,104 | 13,038 | 0.948 ms | 1.278 ms | 1.291 ms |
560
+ | Mixed streams | 43,795 | 34,196 | 37,116 | 0.463 ms | 0.523 ms | 0.473 ms |
561
+ | Bulk transfer (MiB/s) | 2,009.9 | 1,264.9 | 1,313.3 | 15.058 ms | 25.219 ms | 24.317 ms |
529
562
 
530
563
  The former single-burst idle-activation row has been retired: its traffic
531
564
  phase lasted only a few milliseconds and produced unstable throughput rankings.
532
565
  Idle activation now has a separate, versioned latency benchmark described below.
533
- The other measurements above are unchanged. HTTP's 0.3% difference is too small
534
- to call a win.
535
-
536
- Compared with the same sustained workload on the pre-optimization build,
537
- plain-text `websockets`, aiohttp, and Starlette throughput improved by 10.2%,
538
- 19.4%, and 22.3%, respectively. They still trail uvloop. The historical regression
539
- gate also flagged HTTP tail latency and legacy idle activation; this is not an
540
- across-the-board performance win. See the
566
+ In this run, zuvloop had the highest plaintext HTTP and WebSocket throughput,
567
+ while rsloop led TLS throughput, mixed streams, and bulk transfer. Throughput
568
+ and tail latency do not always agree: rsloop's raw WebSocket p95 was higher
569
+ than both alternatives, including over TLS. These results are not an
570
+ across-the-board performance win or a before/after regression measurement. See the
541
571
  [benchmark documentation](./benches/README.md) for workload definitions and
542
572
  reproduction commands.
543
573
 
@@ -550,41 +580,50 @@ so has more to lose when cores are contended.
550
580
  ### Idle activation latency
551
581
 
552
582
  ```bash
553
- .venv/bin/python benches/workload_matrix.py \
554
- --loops rsloop,uvloop --scenarios idle_connections --repeat 8 \
583
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
584
+ --loops rsloop,uvloop,zuvloop --scenarios idle_connections --repeat 9 \
555
585
  --idle-cycles 100 --idle-warmup-cycles 5 --idle-seconds 0.2 \
556
586
  --json-output target/idle-v2-paired.json
557
587
  ```
558
588
 
559
589
  Idle v2 reuses 200 established connections across repeated idle/wakeup cycles.
560
590
  It measures all replies from one shared activation timestamp, including task
561
- scheduling delay, and reports first/50%/95%/all-reply latency. Eight fresh-process
562
- blocks alternate loop order; confidence intervals resample whole paired runs,
591
+ scheduling delay, and reports first/50%/95%/all-reply latency. Nine fresh-process
592
+ blocks rotate loop order so each loop runs first three times; confidence intervals resample whole paired runs,
563
593
  not individual connections. Results are classified as improved, regressed, or
564
594
  inconclusive using a 5% practical threshold and an approximate 95% confidence
565
- interval. The command takes about six minutes; use `--idle-cycles 3
595
+ interval. The command takes about ten minutes; use `--idle-cycles 3
566
596
  --idle-warmup-cycles 1 --idle-seconds 0.01 --repeat 1` for a smoke test only.
567
597
 
568
598
  The new measurements cannot be compared with the retired ops/s row. See
569
599
  [benchmark methodology and regression handling](./benches/README.md#idle-activation-v2)
570
600
  for timing definitions, host controls, raw distributions, and sample requirements.
571
601
 
572
- Validation on the Linux/i9-9900K host above collected 800 measured cycles per
573
- loop in 16 distinct processes, with unrestricted affinity and no concurrent
574
- builds or test runs. These are medians across runs of each run's median cycle
575
- milestone, in milliseconds (lower is better):
602
+ Measured on September 7, 2026 at commit `6cc3444` on the Linux/i9-9900K
603
+ host above with CPython 3.14.7, rsloop 0.1.49 (release), uvloop 0.22.1,
604
+ and zuvloop 0.0.14. The run collected 900 measured cycles per loop in 27
605
+ distinct processes, with unrestricted affinity and no concurrent builds or
606
+ test runs. These are medians across runs of each run's median cycle milestone,
607
+ in milliseconds (lower is better):
576
608
 
577
609
  | Loop | First reply | 50% replied | 95% replied | All replied |
578
610
  | --- | ---: | ---: | ---: | ---: |
579
- | rsloop | 17.510 | 17.893 | 18.169 | 18.299 |
580
- | uvloop | 18.072 | 18.584 | 19.029 | 19.076 |
581
-
582
- The preselected paired comparison is **inconclusive**: the geometric mean
583
- run-level p95 latency change for rsloop versus uvloop is +9.6%, with a 95%
584
- bootstrap interval of [-4.8%, +32.2%]. This uses paired run ratios, not the
585
- ratio of the table's medians. Individual cycle-p95 latencies still form fast
586
- and slow clusters (rsloop 3.423–28.634 ms; uvloop 3.708–24.329 ms). The new
587
- benchmark exposes that uncertainty rather than declaring a throughput winner.
611
+ | rsloop | 17.221 | 17.579 | 17.857 | 17.991 |
612
+ | uvloop | 18.193 | 18.722 | 19.174 | 19.221 |
613
+ | zuvloop | 16.441 | 16.869 | 17.248 | 17.288 |
614
+
615
+ Comparisons against uvloop use geometric mean paired process-run ratios,
616
+ not ratios of the table medians:
617
+
618
+ - rsloop: -23.5% p95 latency, approximate 95% interval [-48.6%, +5.9%]; **inconclusive** at the 5% threshold.
619
+ - zuvloop: -15.2% p95 latency, approximate 95% interval [-31.0%, +4.5%]; **inconclusive** at the 5% threshold.
620
+
621
+ Individual cycle-p95 latencies span 3.374–28.184 ms for rsloop,
622
+ 3.736–24.228 ms for uvloop, and 3.454–26.163 ms for zuvloop.
623
+ Median ordering alone does not establish a latency win.
624
+
625
+ The [full report](./benches/full-benchmark-6cc3444.md) links the recorded
626
+ measurements and documents the settings used for all three benchmark suites.
588
627
 
589
628
  See [`benches/README.md`](./benches/README.md) for workload details and
590
629
  extra flags, and [`examples/README.md`](./examples/README.md) for the FastAPI