rsloop 0.1.48__tar.gz → 0.1.49__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 (260) hide show
  1. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/tests.yml +1 -48
  2. rsloop-0.1.49/.github/workflows/vibeio-native.yml +109 -0
  3. {rsloop-0.1.48 → rsloop-0.1.49}/Cargo.lock +1 -1
  4. {rsloop-0.1.48 → rsloop-0.1.49}/Cargo.toml +1 -1
  5. {rsloop-0.1.48 → rsloop-0.1.49}/PKG-INFO +75 -49
  6. {rsloop-0.1.48 → rsloop-0.1.49}/README.md +74 -48
  7. {rsloop-0.1.48 → rsloop-0.1.49}/benches/README.md +16 -11
  8. {rsloop-0.1.48 → rsloop-0.1.49}/benches/compare_event_loops.py +9 -3
  9. {rsloop-0.1.48 → rsloop-0.1.49}/benches/workload_matrix.py +2 -1
  10. {rsloop-0.1.48 → rsloop-0.1.49}/docs/development.md +12 -0
  11. {rsloop-0.1.48 → rsloop-0.1.49}/justfile +1 -1
  12. {rsloop-0.1.48 → rsloop-0.1.49}/pyproject.toml +9 -2
  13. {rsloop-0.1.48 → rsloop-0.1.49}/qualirs.toml +11 -0
  14. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/test-supported-pythons.sh +3 -1
  15. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/write_queue.rs +8 -0
  16. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/blocking/default.rs +1 -1
  17. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/kqueue.rs +17 -2
  18. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/mio.rs +19 -9
  19. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/fs/mod.rs +4 -1
  20. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/buf.rs +7 -3
  21. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/splice.rs +2 -3
  22. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/util.rs +1 -1
  23. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/lib.rs +2 -0
  24. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/tcp/listener.rs +1 -1
  25. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/tcp/mod.rs +16 -22
  26. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/tcp/stream.rs +1 -1
  27. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/udp.rs +5 -3
  28. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/unix/mod.rs +16 -22
  29. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/accept.rs +27 -20
  30. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/accept_unix.rs +22 -16
  31. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/connect.rs +84 -12
  32. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/io_util.rs +44 -20
  33. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/open.rs +2 -2
  34. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/read.rs +58 -20
  35. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/readv.rs +34 -37
  36. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/recv.rs +1 -1
  37. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/recvfrom.rs +23 -20
  38. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/sendto.rs +1 -1
  39. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/splice.rs +13 -4
  40. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/waitpid.rs +1 -2
  41. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/writeat.rs +1 -1
  42. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/process/mod.rs +16 -3
  43. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/process/reaper.rs +15 -7
  44. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/signal/unix.rs +20 -21
  45. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/signal/windows.rs +7 -16
  46. rsloop-0.1.49/src/vibeio/test_support.rs +281 -0
  47. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/time/sleep.rs +2 -2
  48. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/time/timeout.rs +3 -6
  49. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/timer.rs +41 -34
  50. rsloop-0.1.49/tests/test_benchmark_loops.py +141 -0
  51. {rsloop-0.1.48 → rsloop-0.1.49}/uv.lock +15 -3
  52. {rsloop-0.1.48 → rsloop-0.1.49}/.cargo/config.toml +0 -0
  53. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/anyio.yml +0 -0
  54. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/kani.yml +0 -0
  55. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/mkdocs.yml +0 -0
  56. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/update-version.yml +0 -0
  57. {rsloop-0.1.48 → rsloop-0.1.49}/.github/workflows/wheels.yml +0 -0
  58. {rsloop-0.1.48 → rsloop-0.1.49}/.gitignore +0 -0
  59. {rsloop-0.1.48 → rsloop-0.1.49}/.python-version +0 -0
  60. {rsloop-0.1.48 → rsloop-0.1.49}/.vscode/settings.json +0 -0
  61. {rsloop-0.1.48 → rsloop-0.1.49}/LICENSE +0 -0
  62. {rsloop-0.1.48 → rsloop-0.1.49}/benches/check_regression.py +0 -0
  63. {rsloop-0.1.48 → rsloop-0.1.49}/benches/idle_statistics.py +0 -0
  64. {rsloop-0.1.48 → rsloop-0.1.49}/benches/runtime.rs +0 -0
  65. {rsloop-0.1.48 → rsloop-0.1.49}/benches/timer.rs +0 -0
  66. {rsloop-0.1.48 → rsloop-0.1.49}/benches/vibeio-performance.md +0 -0
  67. {rsloop-0.1.48 → rsloop-0.1.49}/build.rs +0 -0
  68. {rsloop-0.1.48 → rsloop-0.1.49}/docs/examples.md +0 -0
  69. {rsloop-0.1.48 → rsloop-0.1.49}/docs/getting-started.md +0 -0
  70. {rsloop-0.1.48 → rsloop-0.1.49}/docs/how-it-works.md +0 -0
  71. {rsloop-0.1.48 → rsloop-0.1.49}/docs/index.md +0 -0
  72. {rsloop-0.1.48 → rsloop-0.1.49}/docs/project-structure.md +0 -0
  73. {rsloop-0.1.48 → rsloop-0.1.49}/docs/rsloop.png +0 -0
  74. {rsloop-0.1.48 → rsloop-0.1.49}/docs/rust-extensions.md +0 -0
  75. {rsloop-0.1.48 → rsloop-0.1.49}/docs/verification.md +0 -0
  76. {rsloop-0.1.48 → rsloop-0.1.49}/docs/vibeio-cleanup.md +0 -0
  77. {rsloop-0.1.48 → rsloop-0.1.49}/docs/vibeio-findings.md +0 -0
  78. {rsloop-0.1.48 → rsloop-0.1.49}/examples/01_basics.py +0 -0
  79. {rsloop-0.1.48 → rsloop-0.1.49}/examples/02_fd_and_sockets.py +0 -0
  80. {rsloop-0.1.48 → rsloop-0.1.49}/examples/03_streams.py +0 -0
  81. {rsloop-0.1.48 → rsloop-0.1.49}/examples/04_unix_and_accepted_socket.py +0 -0
  82. {rsloop-0.1.48 → rsloop-0.1.49}/examples/05_pipes_signals_subprocesses.py +0 -0
  83. {rsloop-0.1.48 → rsloop-0.1.49}/examples/README.md +0 -0
  84. {rsloop-0.1.48 → rsloop-0.1.49}/examples/fastapi_service.py +0 -0
  85. {rsloop-0.1.48 → rsloop-0.1.49}/examples/picows_server.py +0 -0
  86. {rsloop-0.1.48 → rsloop-0.1.49}/examples/picows_test.py +0 -0
  87. {rsloop-0.1.48 → rsloop-0.1.49}/examples/wsbench_websockets.py +0 -0
  88. {rsloop-0.1.48 → rsloop-0.1.49}/mkdocs.yml +0 -0
  89. {rsloop-0.1.48 → rsloop-0.1.49}/pyrightconfig.json +0 -0
  90. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/__init__.py +0 -0
  91. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/__init__.pyi +0 -0
  92. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/_bootstrap.py +0 -0
  93. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/_loop.pyi +0 -0
  94. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/_loop_compat.py +0 -0
  95. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/_profile.py +0 -0
  96. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/_run.py +0 -0
  97. {rsloop-0.1.48 → rsloop-0.1.49}/python/rsloop/py.typed +0 -0
  98. {rsloop-0.1.48 → rsloop-0.1.49}/rust-toolchain.toml +0 -0
  99. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/build-pgo-wheels.sh +0 -0
  100. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/build-wheels.sh +0 -0
  101. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/generate_test_tls_certs.py +0 -0
  102. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/prepare_anyio_rsloop_tests.py +0 -0
  103. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/python-versions.sh +0 -0
  104. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/run_python_tests.py +0 -0
  105. {rsloop-0.1.48 → rsloop-0.1.49}/scripts/run_rust_tests.py +0 -0
  106. {rsloop-0.1.48 → rsloop-0.1.49}/src/async_event.rs +0 -0
  107. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/asyncgens.rs +0 -0
  108. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/asyncio_cache.rs +0 -0
  109. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/connections.rs +0 -0
  110. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/executor.rs +0 -0
  111. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/ffi_helpers.rs +0 -0
  112. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/lifecycle.rs +0 -0
  113. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/methods.rs +0 -0
  114. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/pipes.rs +0 -0
  115. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/pre_exec.rs +0 -0
  116. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/process_handles.rs +0 -0
  117. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/process_spawn.rs +0 -0
  118. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/process_stdio.rs +0 -0
  119. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/servers.rs +0 -0
  120. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/signals.rs +0 -0
  121. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/sock_ops.rs +0 -0
  122. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/socket_connect.rs +0 -0
  123. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/sockets.rs +0 -0
  124. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/spawn_env.rs +0 -0
  125. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/tasks.rs +0 -0
  126. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/tls_params.rs +0 -0
  127. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api/watchers.rs +0 -0
  128. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/loop_api.rs +0 -0
  129. {rsloop-0.1.48 → rsloop-0.1.49}/src/bindings/mod.rs +0 -0
  130. {rsloop-0.1.48 → rsloop-0.1.49}/src/blocking.rs +0 -0
  131. {rsloop-0.1.48 → rsloop-0.1.49}/src/build_metadata.rs +0 -0
  132. {rsloop-0.1.48 → rsloop-0.1.49}/src/context.rs +0 -0
  133. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/callbacks.rs +0 -0
  134. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/commands.rs +0 -0
  135. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/dispatcher/timer_entry.rs +0 -0
  136. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/dispatcher.rs +0 -0
  137. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/loop_core.rs +0 -0
  138. {rsloop-0.1.48 → rsloop-0.1.49}/src/engine/mod.rs +0 -0
  139. {rsloop-0.1.48 → rsloop-0.1.49}/src/errors.rs +0 -0
  140. {rsloop-0.1.48 → rsloop-0.1.49}/src/lib.rs +0 -0
  141. {rsloop-0.1.48 → rsloop-0.1.49}/src/module_init.rs +0 -0
  142. {rsloop-0.1.48 → rsloop-0.1.49}/src/platform/fd/mod.rs +0 -0
  143. {rsloop-0.1.48 → rsloop-0.1.49}/src/platform/fd/windows.rs +0 -0
  144. {rsloop-0.1.48 → rsloop-0.1.49}/src/platform/mod.rs +0 -0
  145. {rsloop-0.1.48 → rsloop-0.1.49}/src/platform/windows_vibeio.rs +0 -0
  146. {rsloop-0.1.48 → rsloop-0.1.49}/src/profiler.rs +0 -0
  147. {rsloop-0.1.48 → rsloop-0.1.49}/src/python_names.rs +0 -0
  148. {rsloop-0.1.48 → rsloop-0.1.49}/src/rust_async.rs +0 -0
  149. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/mod.rs +0 -0
  150. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/core_events.rs +0 -0
  151. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/core_protocol.rs +0 -0
  152. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/core_state.rs +0 -0
  153. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/mod.rs +0 -0
  154. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/params.rs +0 -0
  155. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/py_pipes.rs +0 -0
  156. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/py_transport.rs +0 -0
  157. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/spawn.rs +0 -0
  158. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/process/worker.rs +0 -0
  159. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/accept.rs +0 -0
  160. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/buffers.rs +0 -0
  161. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/builder.rs +0 -0
  162. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/connect.rs +0 -0
  163. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/core_events.rs +0 -0
  164. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/core_protocol.rs +0 -0
  165. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/core_state.rs +0 -0
  166. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/core_write.rs +0 -0
  167. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/fast.rs +0 -0
  168. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/io_targets.rs +0 -0
  169. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/mod.rs +0 -0
  170. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/pipe_transport.rs +0 -0
  171. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/platform.rs +0 -0
  172. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/poll.rs +0 -0
  173. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/protocol.rs +0 -0
  174. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/py_server.rs +0 -0
  175. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/py_transport.rs +0 -0
  176. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/reader.rs +0 -0
  177. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/reader_task.rs +0 -0
  178. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/server.rs +0 -0
  179. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/server_core.rs +0 -0
  180. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/server_types.rs +0 -0
  181. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/socket_transport.rs +0 -0
  182. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/stats.rs +0 -0
  183. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/tls_session.rs +0 -0
  184. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/tls_transport.rs +0 -0
  185. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/tuning.rs +0 -0
  186. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/worker.rs +0 -0
  187. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/stream/writer.rs +0 -0
  188. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/tls/material.rs +0 -0
  189. {rsloop-0.1.48 → rsloop-0.1.49}/src/transport/tls/mod.rs +0 -0
  190. {rsloop-0.1.48 → rsloop-0.1.49}/src/verification.rs +0 -0
  191. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/UPSTREAM.md +0 -0
  192. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/blocking/mod.rs +0 -0
  193. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/builder.rs +0 -0
  194. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/iocp.rs +0 -0
  195. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/mock.rs +0 -0
  196. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/mod.rs +0 -0
  197. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/driver/uring.rs +0 -0
  198. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/executor.rs +0 -0
  199. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/fd_inner.rs +0 -0
  200. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/fs/file.rs +0 -0
  201. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/fs/metadata.rs +0 -0
  202. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/fs/open_options.rs +0 -0
  203. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/mod.rs +0 -0
  204. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/pipe.rs +0 -0
  205. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/io/stdio.rs +0 -0
  206. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/mod.rs +0 -0
  207. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/unix/listener.rs +0 -0
  208. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/net/unix/stream.rs +0 -0
  209. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/fsync.rs +0 -0
  210. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/hard_link.rs +0 -0
  211. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/mkdir.rs +0 -0
  212. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/mod.rs +0 -0
  213. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/readat.rs +0 -0
  214. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/readiness.rs +0 -0
  215. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/rename.rs +0 -0
  216. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/send.rs +0 -0
  217. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/socket_addr.rs +0 -0
  218. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/statx.rs +0 -0
  219. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/symlink.rs +0 -0
  220. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/unlink.rs +0 -0
  221. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/write.rs +0 -0
  222. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/op/writev.rs +0 -0
  223. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/signal/mod.rs +0 -0
  224. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/task.rs +0 -0
  225. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/time/interval.rs +0 -0
  226. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/time/mod.rs +0 -0
  227. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/util/async_wrap.rs +0 -0
  228. {rsloop-0.1.48 → rsloop-0.1.49}/src/vibeio/util/mod.rs +0 -0
  229. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/_smoke.py +0 -0
  230. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/aiohttp_test.py +0 -0
  231. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/anyio_test.py +0 -0
  232. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/beanie_test.py +0 -0
  233. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/daphne_test.py +0 -0
  234. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/django_asgi_test.py +0 -0
  235. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/django_orm_test.py +0 -0
  236. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/edgy_test.py +0 -0
  237. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/falcon_test.py +0 -0
  238. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/fastapi_test.py +0 -0
  239. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/faststream_test.py +0 -0
  240. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/hypercorn_test.py +0 -0
  241. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/litestar_granian_test.py +0 -0
  242. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/litestar_test.py +0 -0
  243. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/mangum_test.py +0 -0
  244. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/ormar_test.py +0 -0
  245. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/piccolo_test.py +0 -0
  246. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/quart_test.py +0 -0
  247. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/sanic_test.py +0 -0
  248. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/sqlalchemy_test.py +0 -0
  249. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/sqlmodel_test.py +0 -0
  250. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/starlette_test.py +0 -0
  251. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/tortoise_orm_test.py +0 -0
  252. {rsloop-0.1.48 → rsloop-0.1.49}/tests/packages/uvicorn_test.py +0 -0
  253. {rsloop-0.1.48 → rsloop-0.1.49}/tests/proptest-regressions/transport/stream/fast.txt +0 -0
  254. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_compat.py +0 -0
  255. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_free_threading.py +0 -0
  256. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_idle_benchmark.py +0 -0
  257. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_public_api.py +0 -0
  258. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_run.py +0 -0
  259. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_stream_reader.py +0 -0
  260. {rsloop-0.1.48 → rsloop-0.1.49}/tests/test_tls.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.49"
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.49"
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.49
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,32 @@ 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
500
  ```
501
501
 
502
- An example output from that script on macOS (arm64) with CPython 3.14:
502
+ ### Four-loop comparison on Linux
503
+
504
+ Measured on September 7, 2026 on an Intel Core i9-9900K, Linux
505
+ 7.0.0-31-generic (x86_64), and CPython 3.14, with rsloop 0.1.48 built in
506
+ release mode. Each entry is the median of five measured runs after one warmup,
507
+ with each run in a fresh subprocess. Times are milliseconds; lower is better.
508
+
509
+ | Workload | asyncio | uvloop | zuvloop | rsloop |
510
+ |---|---:|---:|---:|---:|
511
+ | 200,000 callbacks | 109.24 | 51.03 | **36.97** | 45.80 |
512
+ | 50,000 tasks | 148.07 | 88.56 | **77.48** | 87.05 |
513
+ | 5,000 TCP roundtrips | 149.27 | 122.88 | 105.12 | **90.62** |
514
+
515
+ The TCP workload uses 1,024-byte payloads and rsloop's native fast streams;
516
+ the other loops use stdlib asyncio streams. Use `--no-rsloop-fast-streams`
517
+ to compare all loops through the stdlib streams layer. Zuvloop led callbacks
518
+ and tasks in this run, while rsloop led TCP roundtrips. These are local
519
+ microbenchmarks, not isolated-lab measurements or general application performance
520
+ claims; do not compare them directly with the historical macOS results below.
521
+
522
+ ### Historical macOS comparison
523
+
524
+ An earlier example output from the script on macOS (arm64) with CPython 3.14:
503
525
 
504
526
  ```
505
527
  callbacks (200,000 ops)
@@ -525,51 +547,51 @@ The production-shaped workload matrix exercises HTTP, WebSocket libraries,
525
547
  TLS, mixed message sizes, backpressure, and connection lifecycle behavior:
526
548
 
527
549
  ```bash
528
- uv run --with uvloop python benches/workload_matrix.py \
529
- --loops rsloop,uvloop \
550
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
551
+ --loops rsloop,uvloop,zuvloop \
530
552
  --sustained \
531
- --json-output target/matrix-opt-final.json
553
+ --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 \
554
+ --json-output target/matrix-zuvloop.json
532
555
  ```
533
556
 
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
557
+ Measured on September 7, 2026 with an Intel Core i9-9900K, Linux
558
+ 7.0.0-31-generic (x86_64), CPython 3.14.7, rsloop 0.1.48 (release build),
559
+ uvloop 0.22.1, and zuvloop 0.0.14. Each row reports the median of seven measured runs after two
537
560
  warmups, using 16 concurrent connections and 500 requests per
538
561
  connection. Throughput is traffic-only operations per second, except for
539
562
  `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%`.
563
+ of each run's p95 latency. Higher throughput and lower latency are better.
564
+ Each loop/scenario pair runs in its own subprocess, with warmups and measured
565
+ runs sharing that process. Loops run sequentially in the order shown.
541
566
 
542
- WebSocket library versions were websockets 17.0.1, aiohttp 3.14.3, Starlette
567
+ WebSocket library versions were websockets 17.1, aiohttp 3.14.3, Starlette
543
568
  1.6.0, and uvicorn 0.52.3. The run used unrestricted CPU affinity, with other
544
569
  host services running but no concurrent builds or tests. These measurements
545
570
  are from a different host than the macOS microbenchmark example above.
546
571
 
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 |
572
+ | Scenario | rsloop | uvloop | zuvloop | rsloop p95 | uvloop p95 | zuvloop p95 |
573
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
574
+ | HTTP keep-alive | 54,357 | 48,364 | 56,826 | 0.335 ms | 0.366 ms | 0.297 ms |
575
+ | TLS HTTP | 69,047 | 25,267 | 24,076 | 0.287 ms | 0.663 ms | 0.700 ms |
576
+ | Raw WebSocket | 4,834 | 4,858 | 4,932 | 5.244 ms | 3.878 ms | 3.381 ms |
577
+ | Raw WebSocket over TLS | 4,875 | 4,457 | 4,421 | 4.425 ms | 4.020 ms | 3.770 ms |
578
+ | `websockets` | 23,878 | 26,237 | 27,928 | 0.784 ms | 0.640 ms | 0.606 ms |
579
+ | `websockets` over TLS | 26,946 | 14,933 | 13,640 | 0.640 ms | 1.103 ms | 1.319 ms |
580
+ | aiohttp WebSocket | 31,145 | 34,552 | 37,733 | 0.625 ms | 0.498 ms | 0.458 ms |
581
+ | aiohttp WebSocket over TLS | 33,730 | 18,576 | 18,245 | 0.517 ms | 0.905 ms | 0.929 ms |
582
+ | Starlette WebSocket | 18,992 | 20,695 | 21,176 | 0.964 ms | 0.816 ms | 0.873 ms |
583
+ | Starlette WebSocket over TLS | 17,876 | 13,454 | 12,487 | 0.955 ms | 1.243 ms | 1.437 ms |
584
+ | Mixed streams | 41,260 | 35,805 | 37,094 | 0.483 ms | 0.504 ms | 0.476 ms |
585
+ | Bulk transfer (MiB/s) | 2,120.0 | 1,269.6 | 1,287.8 | 14.114 ms | 25.134 ms | 24.801 ms |
561
586
 
562
587
  The former single-burst idle-activation row has been retired: its traffic
563
588
  phase lasted only a few milliseconds and produced unstable throughput rankings.
564
589
  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
590
+ In this run, zuvloop had the highest plaintext HTTP and WebSocket throughput,
591
+ while rsloop led TLS throughput, mixed streams, and bulk transfer. Throughput
592
+ and tail latency do not always agree: rsloop's raw WebSocket p95 was higher
593
+ than both alternatives, including over TLS. These results are not an
594
+ across-the-board performance win or a before/after regression measurement. See the
573
595
  [benchmark documentation](./benches/README.md) for workload definitions and
574
596
  reproduction commands.
575
597
 
@@ -582,41 +604,45 @@ so has more to lose when cores are contended.
582
604
  ### Idle activation latency
583
605
 
584
606
  ```bash
585
- .venv/bin/python benches/workload_matrix.py \
586
- --loops rsloop,uvloop --scenarios idle_connections --repeat 8 \
607
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
608
+ --loops rsloop,uvloop,zuvloop --scenarios idle_connections --repeat 9 \
587
609
  --idle-cycles 100 --idle-warmup-cycles 5 --idle-seconds 0.2 \
588
610
  --json-output target/idle-v2-paired.json
589
611
  ```
590
612
 
591
613
  Idle v2 reuses 200 established connections across repeated idle/wakeup cycles.
592
614
  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,
615
+ scheduling delay, and reports first/50%/95%/all-reply latency. Nine fresh-process
616
+ blocks rotate loop order so each loop runs first three times; confidence intervals resample whole paired runs,
595
617
  not individual connections. Results are classified as improved, regressed, or
596
618
  inconclusive using a 5% practical threshold and an approximate 95% confidence
597
- interval. The command takes about six minutes; use `--idle-cycles 3
619
+ interval. The command takes about ten minutes; use `--idle-cycles 3
598
620
  --idle-warmup-cycles 1 --idle-seconds 0.01 --repeat 1` for a smoke test only.
599
621
 
600
622
  The new measurements cannot be compared with the retired ops/s row. See
601
623
  [benchmark methodology and regression handling](./benches/README.md#idle-activation-v2)
602
624
  for timing definitions, host controls, raw distributions, and sample requirements.
603
625
 
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
626
+ Measured on September 7, 2026 on the Linux/i9-9900K host above with CPython
627
+ 3.14.7, rsloop 0.1.48 (release), uvloop 0.22.1, and zuvloop 0.0.14.
628
+ The run collected 900 measured cycles per loop in 27 distinct processes,
629
+ with unrestricted affinity and no concurrent
606
630
  builds or test runs. These are medians across runs of each run's median cycle
607
631
  milestone, in milliseconds (lower is better):
608
632
 
609
633
  | Loop | First reply | 50% replied | 95% replied | All replied |
610
634
  | --- | ---: | ---: | ---: | ---: |
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.
635
+ | rsloop | 12.605 | 12.868 | 13.059 | 13.146 |
636
+ | uvloop | 13.564 | 13.927 | 14.219 | 14.247 |
637
+ | zuvloop | 16.331 | 16.772 | 17.146 | 17.206 |
638
+
639
+ Both paired comparisons against uvloop are **inconclusive**. The geometric mean
640
+ run-level p95 latency change is -13.7% for rsloop, with a 95% bootstrap interval
641
+ of [-27.5%, +3.1%], and -0.4% for zuvloop, with an interval of [-21.5%, +25.6%].
642
+ These use paired run ratios, not the ratio of the table's medians. Individual
643
+ cycle-p95 latencies span 3.444–29.529 ms for rsloop, 3.739–24.965 ms for uvloop,
644
+ and 3.388–29.540 ms for zuvloop. The benchmark exposes that uncertainty rather
645
+ than declaring a latency winner from the median ordering alone.
620
646
 
621
647
  See [`benches/README.md`](./benches/README.md) for workload details and
622
648
  extra flags, and [`examples/README.md`](./examples/README.md) for the FastAPI
@@ -464,10 +464,32 @@ 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
468
  ```
469
469
 
470
- An example output from that script on macOS (arm64) with CPython 3.14:
470
+ ### Four-loop comparison on Linux
471
+
472
+ Measured on September 7, 2026 on an Intel Core i9-9900K, Linux
473
+ 7.0.0-31-generic (x86_64), and CPython 3.14, with rsloop 0.1.48 built in
474
+ release mode. Each entry is the median of five measured runs after one warmup,
475
+ with each run in a fresh subprocess. Times are milliseconds; lower is better.
476
+
477
+ | Workload | asyncio | uvloop | zuvloop | rsloop |
478
+ |---|---:|---:|---:|---:|
479
+ | 200,000 callbacks | 109.24 | 51.03 | **36.97** | 45.80 |
480
+ | 50,000 tasks | 148.07 | 88.56 | **77.48** | 87.05 |
481
+ | 5,000 TCP roundtrips | 149.27 | 122.88 | 105.12 | **90.62** |
482
+
483
+ The TCP workload uses 1,024-byte payloads and rsloop's native fast streams;
484
+ the other loops use stdlib asyncio streams. Use `--no-rsloop-fast-streams`
485
+ to compare all loops through the stdlib streams layer. Zuvloop led callbacks
486
+ and tasks in this run, while rsloop led TCP roundtrips. These are local
487
+ microbenchmarks, not isolated-lab measurements or general application performance
488
+ claims; do not compare them directly with the historical macOS results below.
489
+
490
+ ### Historical macOS comparison
491
+
492
+ An earlier example output from the script on macOS (arm64) with CPython 3.14:
471
493
 
472
494
  ```
473
495
  callbacks (200,000 ops)
@@ -493,51 +515,51 @@ The production-shaped workload matrix exercises HTTP, WebSocket libraries,
493
515
  TLS, mixed message sizes, backpressure, and connection lifecycle behavior:
494
516
 
495
517
  ```bash
496
- uv run --with uvloop python benches/workload_matrix.py \
497
- --loops rsloop,uvloop \
518
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
519
+ --loops rsloop,uvloop,zuvloop \
498
520
  --sustained \
499
- --json-output target/matrix-opt-final.json
521
+ --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 \
522
+ --json-output target/matrix-zuvloop.json
500
523
  ```
501
524
 
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
525
+ Measured on September 7, 2026 with an Intel Core i9-9900K, Linux
526
+ 7.0.0-31-generic (x86_64), CPython 3.14.7, rsloop 0.1.48 (release build),
527
+ uvloop 0.22.1, and zuvloop 0.0.14. Each row reports the median of seven measured runs after two
505
528
  warmups, using 16 concurrent connections and 500 requests per
506
529
  connection. Throughput is traffic-only operations per second, except for
507
530
  `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%`.
531
+ of each run's p95 latency. Higher throughput and lower latency are better.
532
+ Each loop/scenario pair runs in its own subprocess, with warmups and measured
533
+ runs sharing that process. Loops run sequentially in the order shown.
509
534
 
510
- WebSocket library versions were websockets 17.0.1, aiohttp 3.14.3, Starlette
535
+ WebSocket library versions were websockets 17.1, aiohttp 3.14.3, Starlette
511
536
  1.6.0, and uvicorn 0.52.3. The run used unrestricted CPU affinity, with other
512
537
  host services running but no concurrent builds or tests. These measurements
513
538
  are from a different host than the macOS microbenchmark example above.
514
539
 
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 |
540
+ | Scenario | rsloop | uvloop | zuvloop | rsloop p95 | uvloop p95 | zuvloop p95 |
541
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
542
+ | HTTP keep-alive | 54,357 | 48,364 | 56,826 | 0.335 ms | 0.366 ms | 0.297 ms |
543
+ | TLS HTTP | 69,047 | 25,267 | 24,076 | 0.287 ms | 0.663 ms | 0.700 ms |
544
+ | Raw WebSocket | 4,834 | 4,858 | 4,932 | 5.244 ms | 3.878 ms | 3.381 ms |
545
+ | Raw WebSocket over TLS | 4,875 | 4,457 | 4,421 | 4.425 ms | 4.020 ms | 3.770 ms |
546
+ | `websockets` | 23,878 | 26,237 | 27,928 | 0.784 ms | 0.640 ms | 0.606 ms |
547
+ | `websockets` over TLS | 26,946 | 14,933 | 13,640 | 0.640 ms | 1.103 ms | 1.319 ms |
548
+ | aiohttp WebSocket | 31,145 | 34,552 | 37,733 | 0.625 ms | 0.498 ms | 0.458 ms |
549
+ | aiohttp WebSocket over TLS | 33,730 | 18,576 | 18,245 | 0.517 ms | 0.905 ms | 0.929 ms |
550
+ | Starlette WebSocket | 18,992 | 20,695 | 21,176 | 0.964 ms | 0.816 ms | 0.873 ms |
551
+ | Starlette WebSocket over TLS | 17,876 | 13,454 | 12,487 | 0.955 ms | 1.243 ms | 1.437 ms |
552
+ | Mixed streams | 41,260 | 35,805 | 37,094 | 0.483 ms | 0.504 ms | 0.476 ms |
553
+ | Bulk transfer (MiB/s) | 2,120.0 | 1,269.6 | 1,287.8 | 14.114 ms | 25.134 ms | 24.801 ms |
529
554
 
530
555
  The former single-burst idle-activation row has been retired: its traffic
531
556
  phase lasted only a few milliseconds and produced unstable throughput rankings.
532
557
  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
558
+ In this run, zuvloop had the highest plaintext HTTP and WebSocket throughput,
559
+ while rsloop led TLS throughput, mixed streams, and bulk transfer. Throughput
560
+ and tail latency do not always agree: rsloop's raw WebSocket p95 was higher
561
+ than both alternatives, including over TLS. These results are not an
562
+ across-the-board performance win or a before/after regression measurement. See the
541
563
  [benchmark documentation](./benches/README.md) for workload definitions and
542
564
  reproduction commands.
543
565
 
@@ -550,41 +572,45 @@ so has more to lose when cores are contended.
550
572
  ### Idle activation latency
551
573
 
552
574
  ```bash
553
- .venv/bin/python benches/workload_matrix.py \
554
- --loops rsloop,uvloop --scenarios idle_connections --repeat 8 \
575
+ uv run --with uvloop --with zuvloop python benches/workload_matrix.py \
576
+ --loops rsloop,uvloop,zuvloop --scenarios idle_connections --repeat 9 \
555
577
  --idle-cycles 100 --idle-warmup-cycles 5 --idle-seconds 0.2 \
556
578
  --json-output target/idle-v2-paired.json
557
579
  ```
558
580
 
559
581
  Idle v2 reuses 200 established connections across repeated idle/wakeup cycles.
560
582
  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,
583
+ scheduling delay, and reports first/50%/95%/all-reply latency. Nine fresh-process
584
+ blocks rotate loop order so each loop runs first three times; confidence intervals resample whole paired runs,
563
585
  not individual connections. Results are classified as improved, regressed, or
564
586
  inconclusive using a 5% practical threshold and an approximate 95% confidence
565
- interval. The command takes about six minutes; use `--idle-cycles 3
587
+ interval. The command takes about ten minutes; use `--idle-cycles 3
566
588
  --idle-warmup-cycles 1 --idle-seconds 0.01 --repeat 1` for a smoke test only.
567
589
 
568
590
  The new measurements cannot be compared with the retired ops/s row. See
569
591
  [benchmark methodology and regression handling](./benches/README.md#idle-activation-v2)
570
592
  for timing definitions, host controls, raw distributions, and sample requirements.
571
593
 
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
594
+ Measured on September 7, 2026 on the Linux/i9-9900K host above with CPython
595
+ 3.14.7, rsloop 0.1.48 (release), uvloop 0.22.1, and zuvloop 0.0.14.
596
+ The run collected 900 measured cycles per loop in 27 distinct processes,
597
+ with unrestricted affinity and no concurrent
574
598
  builds or test runs. These are medians across runs of each run's median cycle
575
599
  milestone, in milliseconds (lower is better):
576
600
 
577
601
  | Loop | First reply | 50% replied | 95% replied | All replied |
578
602
  | --- | ---: | ---: | ---: | ---: |
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.
603
+ | rsloop | 12.605 | 12.868 | 13.059 | 13.146 |
604
+ | uvloop | 13.564 | 13.927 | 14.219 | 14.247 |
605
+ | zuvloop | 16.331 | 16.772 | 17.146 | 17.206 |
606
+
607
+ Both paired comparisons against uvloop are **inconclusive**. The geometric mean
608
+ run-level p95 latency change is -13.7% for rsloop, with a 95% bootstrap interval
609
+ of [-27.5%, +3.1%], and -0.4% for zuvloop, with an interval of [-21.5%, +25.6%].
610
+ These use paired run ratios, not the ratio of the table's medians. Individual
611
+ cycle-p95 latencies span 3.444–29.529 ms for rsloop, 3.739–24.965 ms for uvloop,
612
+ and 3.388–29.540 ms for zuvloop. The benchmark exposes that uncertainty rather
613
+ than declaring a latency winner from the median ordering alone.
588
614
 
589
615
  See [`benches/README.md`](./benches/README.md) for workload details and
590
616
  extra flags, and [`examples/README.md`](./examples/README.md) for the FastAPI
@@ -8,14 +8,15 @@ before/after results and limitations; these are not Python event-loop comparison
8
8
  This benchmark compares:
9
9
 
10
10
  - stdlib `asyncio`
11
- - `uvloop`
11
+ - `uvloop` (`winloop` on Windows)
12
+ - `zuvloop` on Python 3.14+
12
13
  - the Rust prototype in `rsloop`
13
14
 
14
15
  Run it from the repository root so Python resolves the editable Rust package cleanly:
15
16
 
16
17
  ```bash
17
18
  uv run --with maturin maturin develop --release
18
- uv run --with uvloop python benches/compare_event_loops.py
19
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py
19
20
  ```
20
21
 
21
22
  Benchmark runner: [`benches/compare_event_loops.py`](./compare_event_loops.py)
@@ -28,7 +29,7 @@ Useful quick run:
28
29
 
29
30
  ```bash
30
31
  uv run --with maturin maturin develop --release
31
- uv run --with uvloop python benches/compare_event_loops.py \
32
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py \
32
33
  --warmups 0 \
33
34
  --repeat 3 \
34
35
  --callbacks 50000 \
@@ -42,7 +43,7 @@ measured runs, add a label directory:
42
43
 
43
44
  ```bash
44
45
  uv run --with maturin maturin develop --release --features profiler
45
- uv run --with uvloop python benches/compare_event_loops.py \
46
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py \
46
47
  --loops rsloop \
47
48
  --workloads callbacks,tasks,tcp_streams \
48
49
  --profile-rsloop-dir benches/profiles
@@ -68,10 +69,10 @@ Current workloads:
68
69
  - `tcp_streams`: local `asyncio.start_server()` / `asyncio.open_connection()` echo round trips
69
70
 
70
71
  By default, `tcp_streams` uses `rsloop`'s native fast streams. If you want all
71
- three loops to go through the stdlib `asyncio` streams layer instead, pass:
72
+ selected loops to go through the stdlib `asyncio` streams layer instead, pass:
72
73
 
73
74
  ```bash
74
- uv run --with uvloop python benches/compare_event_loops.py --no-rsloop-fast-streams
75
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/compare_event_loops.py --no-rsloop-fast-streams
75
76
  ```
76
77
 
77
78
  ## Representative workload matrix
@@ -107,18 +108,22 @@ Build rsloop in release mode and run the standard matrix with:
107
108
 
108
109
  ```bash
109
110
  uv run --with maturin maturin develop --release
110
- uv run --with uvloop python benches/workload_matrix.py # Unix
111
- uv run --with winloop python benches/workload_matrix.py # Windows
111
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/workload_matrix.py # Unix
112
+ uv run --with winloop --with 'zuvloop; python_version >= "3.14"' python benches/workload_matrix.py # Windows
112
113
  ```
113
114
 
114
115
  The default comparison is `asyncio,uvloop,rsloop` on Unix. Because uvloop is
115
116
  not available on Windows, the default there is `asyncio,winloop,rsloop`.
116
- Unavailable optional loops are reported and skipped.
117
+ On Python 3.14+, zuvloop is added before rsloop in both defaults. Older Python
118
+ versions retain the three-loop defaults. Select a subset with `--loops`, for
119
+ example `--loops uvloop,zuvloop,rsloop` on Unix with Python 3.14+.
120
+ Unavailable optional loops are reported and skipped; workload failures are not
121
+ silently replaced by another loop. zuvloop is a benchmark-only dependency.
117
122
 
118
123
  For a quick smoke run:
119
124
 
120
125
  ```bash
121
- uv run --with uvloop python benches/workload_matrix.py \
126
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/workload_matrix.py \
122
127
  --warmups 0 \
123
128
  --repeat 1 \
124
129
  --concurrency 4 \
@@ -152,7 +157,7 @@ pass before each rsloop scenario:
152
157
 
153
158
  ```bash
154
159
  uv run --with maturin maturin develop --release --features profiler
155
- uv run --with uvloop python benches/workload_matrix.py \
160
+ uv run --with uvloop --with 'zuvloop; python_version >= "3.14"' python benches/workload_matrix.py \
156
161
  --loops rsloop \
157
162
  --profile-rsloop-dir benches/profiles \
158
163
  --allow-profiler-build