uringmachine 0.24.0 → 0.26.0

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitmodules +0 -3
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile +11 -0
  6. data/README.md +266 -112
  7. data/Rakefile +8 -0
  8. data/TODO.md +14 -21
  9. data/benchmark/common.rb +2 -0
  10. data/benchmark/openssl.rb +77 -0
  11. data/benchmark/openssl_socketpair.rb +112 -0
  12. data/benchmark/sqlite.rb +1 -1
  13. data/docs/design/buffer_pool.md +1 -1
  14. data/docs/wroclove.rb.md +52 -0
  15. data/ext/um/extconf.rb +15 -0
  16. data/ext/um/um.c +392 -358
  17. data/ext/um/um.h +48 -23
  18. data/ext/um/um_async_op.c +9 -8
  19. data/ext/um/um_async_op_class.c +34 -3
  20. data/ext/um/um_class.c +705 -19
  21. data/ext/um/um_const.c +31 -0
  22. data/ext/um/um_mutex_class.c +12 -0
  23. data/ext/um/um_op.c +15 -1
  24. data/ext/um/um_queue_class.c +16 -0
  25. data/ext/um/um_ssl.c +109 -0
  26. data/ext/um/um_stream.c +9 -8
  27. data/ext/um/um_sync.c +18 -11
  28. data/ext/um/um_utils.c +17 -8
  29. data/grant-2025/interim-report.md +1 -1
  30. data/grant-2025/journal.md +4 -4
  31. data/grant-2025/tasks.md +6 -4
  32. data/lib/uringmachine/dns_resolver.rb +38 -0
  33. data/lib/uringmachine/fiber_scheduler.rb +7 -5
  34. data/lib/uringmachine/version.rb +1 -1
  35. data/lib/uringmachine.rb +106 -6
  36. data/test/helper.rb +15 -0
  37. data/test/test_async_op.rb +3 -2
  38. data/test/test_fiber_scheduler.rb +41 -1
  39. data/test/test_ssl.rb +85 -0
  40. data/test/test_stream.rb +11 -0
  41. data/test/test_um.rb +445 -11
  42. data/uringmachine.gemspec +1 -7
  43. data/vendor/liburing/examples/send-zerocopy.c +43 -31
  44. data/vendor/liburing/examples/zcrx.c +260 -69
  45. data/vendor/liburing/liburing.spec +1 -1
  46. data/vendor/liburing/src/include/liburing/io_uring.h +12 -0
  47. data/vendor/liburing/src/include/liburing.h +3 -2
  48. data/vendor/liburing/src/liburing-ffi.map +4 -0
  49. data/vendor/liburing/src/liburing.map +4 -0
  50. data/vendor/liburing/src/queue.c +12 -0
  51. data/vendor/liburing/src/register.c +1 -0
  52. data/vendor/liburing/src/setup.c +15 -7
  53. data/vendor/liburing/test/Makefile +8 -4
  54. data/vendor/liburing/test/conn-unreach.c +1 -1
  55. data/vendor/liburing/test/epwait.c +32 -6
  56. data/vendor/liburing/test/io-wq-exit.c +131 -0
  57. data/vendor/liburing/test/iowait.c +1 -1
  58. data/vendor/liburing/test/min-timeout.c +3 -1
  59. data/vendor/liburing/test/open-close.c +39 -0
  60. data/vendor/liburing/test/poll-update-trigger.c +85 -0
  61. data/vendor/liburing/test/recvsend_bundle.c +14 -11
  62. data/vendor/liburing/test/sendzc-bug.c +146 -0
  63. data/vendor/liburing/test/sqe-mixed-nop.c +151 -7
  64. data/vendor/liburing/test/test.h +2 -0
  65. data/vendor/liburing/test/timestamp-bug.c +135 -0
  66. data/vendor/liburing/test/timestamp.c +5 -0
  67. data/vendor/liburing/test/vec-regbuf.c +136 -1
  68. metadata +38 -283
  69. data/vendor/libressl/.github/scripts/changelog.sh +0 -74
  70. data/vendor/libressl/.github/workflows/android.yml +0 -35
  71. data/vendor/libressl/.github/workflows/cifuzz.yml +0 -33
  72. data/vendor/libressl/.github/workflows/cmake-config.yml +0 -98
  73. data/vendor/libressl/.github/workflows/coverity.yml +0 -69
  74. data/vendor/libressl/.github/workflows/emscripten.yml +0 -71
  75. data/vendor/libressl/.github/workflows/fedora-rawhide.yml +0 -39
  76. data/vendor/libressl/.github/workflows/freebsd.yml +0 -71
  77. data/vendor/libressl/.github/workflows/linux.yml +0 -71
  78. data/vendor/libressl/.github/workflows/macos.yml +0 -37
  79. data/vendor/libressl/.github/workflows/release.yml +0 -81
  80. data/vendor/libressl/.github/workflows/rust-openssl.yml +0 -47
  81. data/vendor/libressl/.github/workflows/solaris.yml +0 -37
  82. data/vendor/libressl/.github/workflows/windows.yml +0 -70
  83. data/vendor/libressl/.gitignore +0 -333
  84. data/vendor/libressl/CMakeLists.txt +0 -581
  85. data/vendor/libressl/COPYING +0 -133
  86. data/vendor/libressl/ChangeLog +0 -3280
  87. data/vendor/libressl/FindLibreSSL.cmake +0 -232
  88. data/vendor/libressl/LibreSSLConfig.cmake.in +0 -36
  89. data/vendor/libressl/Makefile.am +0 -60
  90. data/vendor/libressl/Makefile.am.common +0 -20
  91. data/vendor/libressl/OPENBSD_BRANCH +0 -1
  92. data/vendor/libressl/README.md +0 -238
  93. data/vendor/libressl/README.mingw.md +0 -43
  94. data/vendor/libressl/apps/CMakeLists.txt +0 -18
  95. data/vendor/libressl/apps/Makefile.am +0 -5
  96. data/vendor/libressl/apps/nc/CMakeLists.txt +0 -67
  97. data/vendor/libressl/apps/nc/Makefile.am +0 -64
  98. data/vendor/libressl/apps/nc/compat/accept4.c +0 -17
  99. data/vendor/libressl/apps/nc/compat/readpassphrase.c +0 -205
  100. data/vendor/libressl/apps/nc/compat/socket.c +0 -29
  101. data/vendor/libressl/apps/nc/compat/sys/socket.h +0 -30
  102. data/vendor/libressl/apps/ocspcheck/CMakeLists.txt +0 -44
  103. data/vendor/libressl/apps/ocspcheck/Makefile.am +0 -45
  104. data/vendor/libressl/apps/ocspcheck/compat/.gitignore +0 -0
  105. data/vendor/libressl/apps/openssl/CMakeLists.txt +0 -97
  106. data/vendor/libressl/apps/openssl/Makefile.am +0 -108
  107. data/vendor/libressl/apps/openssl/apps_win.c +0 -138
  108. data/vendor/libressl/apps/openssl/certhash_win.c +0 -13
  109. data/vendor/libressl/apps/openssl/compat/clock_gettime_osx.c +0 -26
  110. data/vendor/libressl/apps/openssl/compat/poll_win.c +0 -329
  111. data/vendor/libressl/appveyor.yml +0 -53
  112. data/vendor/libressl/autogen.sh +0 -15
  113. data/vendor/libressl/check-release.sh +0 -86
  114. data/vendor/libressl/cmake_export_symbol.cmake +0 -71
  115. data/vendor/libressl/cmake_uninstall.cmake.in +0 -36
  116. data/vendor/libressl/config +0 -17
  117. data/vendor/libressl/configure.ac +0 -165
  118. data/vendor/libressl/crypto/CMakeLists.txt +0 -863
  119. data/vendor/libressl/crypto/Makefile.am +0 -962
  120. data/vendor/libressl/crypto/Makefile.am.arc4random +0 -46
  121. data/vendor/libressl/crypto/Makefile.am.elf-mips +0 -14
  122. data/vendor/libressl/crypto/Makefile.am.elf-mips64 +0 -14
  123. data/vendor/libressl/crypto/Makefile.am.elf-x86_64 +0 -35
  124. data/vendor/libressl/crypto/Makefile.am.macosx-x86_64 +0 -35
  125. data/vendor/libressl/crypto/Makefile.am.masm-x86_64 +0 -22
  126. data/vendor/libressl/crypto/Makefile.am.mingw64-x86_64 +0 -23
  127. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_darwin.c +0 -60
  128. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_linux.c +0 -62
  129. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_none.c +0 -26
  130. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_windows.c +0 -36
  131. data/vendor/libressl/crypto/arch/loongarch64/crypto_arch.h +0 -21
  132. data/vendor/libressl/crypto/arch/mips/crypto_arch.h +0 -21
  133. data/vendor/libressl/crypto/bn/arch/loongarch64/bn_arch.h +0 -23
  134. data/vendor/libressl/crypto/bn/arch/mips/bn_arch.h +0 -24
  135. data/vendor/libressl/crypto/compat/.gitignore +0 -31
  136. data/vendor/libressl/crypto/compat/arc4random.h +0 -41
  137. data/vendor/libressl/crypto/compat/b_win.c +0 -55
  138. data/vendor/libressl/crypto/compat/bsd-asprintf.c +0 -96
  139. data/vendor/libressl/crypto/compat/crypto_lock_win.c +0 -56
  140. data/vendor/libressl/crypto/compat/explicit_bzero_win.c +0 -13
  141. data/vendor/libressl/crypto/compat/freezero.c +0 -32
  142. data/vendor/libressl/crypto/compat/getdelim.c +0 -78
  143. data/vendor/libressl/crypto/compat/getline.c +0 -40
  144. data/vendor/libressl/crypto/compat/getopt_long.c +0 -528
  145. data/vendor/libressl/crypto/compat/getpagesize.c +0 -18
  146. data/vendor/libressl/crypto/compat/getprogname_linux.c +0 -23
  147. data/vendor/libressl/crypto/compat/getprogname_unimpl.c +0 -7
  148. data/vendor/libressl/crypto/compat/getprogname_windows.c +0 -13
  149. data/vendor/libressl/crypto/compat/posix_win.c +0 -296
  150. data/vendor/libressl/crypto/compat/syslog_r.c +0 -19
  151. data/vendor/libressl/crypto/compat/ui_openssl_win.c +0 -334
  152. data/vendor/libressl/dist.sh +0 -22
  153. data/vendor/libressl/gen-coverage-report.sh +0 -58
  154. data/vendor/libressl/gen-openbsd-tags.sh +0 -20
  155. data/vendor/libressl/include/CMakeLists.txt +0 -61
  156. data/vendor/libressl/include/Makefile.am +0 -79
  157. data/vendor/libressl/include/arch/loongarch64/opensslconf.h +0 -150
  158. data/vendor/libressl/include/arch/mips/opensslconf.h +0 -150
  159. data/vendor/libressl/include/compat/arpa/inet.h +0 -15
  160. data/vendor/libressl/include/compat/arpa/nameser.h +0 -25
  161. data/vendor/libressl/include/compat/cet.h +0 -19
  162. data/vendor/libressl/include/compat/dirent.h +0 -17
  163. data/vendor/libressl/include/compat/dirent_msvc.h +0 -611
  164. data/vendor/libressl/include/compat/endian.h +0 -161
  165. data/vendor/libressl/include/compat/err.h +0 -95
  166. data/vendor/libressl/include/compat/fcntl.h +0 -32
  167. data/vendor/libressl/include/compat/getopt.h +0 -50
  168. data/vendor/libressl/include/compat/limits.h +0 -25
  169. data/vendor/libressl/include/compat/netdb.h +0 -10
  170. data/vendor/libressl/include/compat/netinet/in.h +0 -19
  171. data/vendor/libressl/include/compat/netinet/ip.h +0 -49
  172. data/vendor/libressl/include/compat/netinet/tcp.h +0 -10
  173. data/vendor/libressl/include/compat/poll.h +0 -63
  174. data/vendor/libressl/include/compat/pthread.h +0 -122
  175. data/vendor/libressl/include/compat/readpassphrase.h +0 -44
  176. data/vendor/libressl/include/compat/resolv.h +0 -24
  177. data/vendor/libressl/include/compat/stdint.h +0 -31
  178. data/vendor/libressl/include/compat/stdio.h +0 -65
  179. data/vendor/libressl/include/compat/stdlib.h +0 -57
  180. data/vendor/libressl/include/compat/string.h +0 -98
  181. data/vendor/libressl/include/compat/sys/_null.h +0 -18
  182. data/vendor/libressl/include/compat/sys/ioctl.h +0 -11
  183. data/vendor/libressl/include/compat/sys/mman.h +0 -19
  184. data/vendor/libressl/include/compat/sys/param.h +0 -15
  185. data/vendor/libressl/include/compat/sys/queue.h +0 -536
  186. data/vendor/libressl/include/compat/sys/select.h +0 -10
  187. data/vendor/libressl/include/compat/sys/socket.h +0 -18
  188. data/vendor/libressl/include/compat/sys/stat.h +0 -129
  189. data/vendor/libressl/include/compat/sys/time.h +0 -37
  190. data/vendor/libressl/include/compat/sys/tree.h +0 -1006
  191. data/vendor/libressl/include/compat/sys/types.h +0 -69
  192. data/vendor/libressl/include/compat/sys/uio.h +0 -17
  193. data/vendor/libressl/include/compat/syslog.h +0 -38
  194. data/vendor/libressl/include/compat/time.h +0 -59
  195. data/vendor/libressl/include/compat/unistd.h +0 -83
  196. data/vendor/libressl/include/compat/win32netcompat.h +0 -57
  197. data/vendor/libressl/include/openssl/Makefile.am.tpl +0 -45
  198. data/vendor/libressl/libcrypto.pc.in +0 -28
  199. data/vendor/libressl/libressl.pub +0 -2
  200. data/vendor/libressl/libssl.pc.in +0 -28
  201. data/vendor/libressl/libtls.pc.in +0 -28
  202. data/vendor/libressl/m4/ax_add_fortify_source.m4 +0 -80
  203. data/vendor/libressl/m4/ax_check_compile_flag.m4 +0 -53
  204. data/vendor/libressl/m4/check-hardening-options.m4 +0 -110
  205. data/vendor/libressl/m4/check-libc.m4 +0 -189
  206. data/vendor/libressl/m4/check-os-options.m4 +0 -181
  207. data/vendor/libressl/m4/disable-compiler-warnings.m4 +0 -44
  208. data/vendor/libressl/man/CMakeLists.txt +0 -26
  209. data/vendor/libressl/man/links +0 -2780
  210. data/vendor/libressl/man/update_links.sh +0 -25
  211. data/vendor/libressl/openssl.pc.in +0 -11
  212. data/vendor/libressl/patches/bn_shift.patch +0 -34
  213. data/vendor/libressl/patches/crypto_arch.h.patch +0 -34
  214. data/vendor/libressl/patches/crypto_namespace.h.patch +0 -22
  215. data/vendor/libressl/patches/netcat.c.patch +0 -178
  216. data/vendor/libressl/patches/openssl.c.patch +0 -12
  217. data/vendor/libressl/patches/opensslfeatures.h.patch +0 -49
  218. data/vendor/libressl/patches/patch-amd64-crypto-cpu-caps.c.patch +0 -20
  219. data/vendor/libressl/patches/patch-i386-crypto-cpu-caps.c.patch +0 -20
  220. data/vendor/libressl/patches/speed.c.patch +0 -114
  221. data/vendor/libressl/patches/ssl_namespace.h.patch +0 -21
  222. data/vendor/libressl/patches/tls.h.patch +0 -16
  223. data/vendor/libressl/patches/tls_config.c.patch +0 -15
  224. data/vendor/libressl/patches/win32_amd64_bn_arch.h.patch +0 -28
  225. data/vendor/libressl/patches/windows_headers.patch +0 -80
  226. data/vendor/libressl/scripts/config.guess +0 -1774
  227. data/vendor/libressl/scripts/config.sub +0 -1907
  228. data/vendor/libressl/scripts/i686-w64-mingw32.cmake +0 -9
  229. data/vendor/libressl/scripts/test +0 -210
  230. data/vendor/libressl/scripts/wrap-compiler-for-flag-check +0 -31
  231. data/vendor/libressl/scripts/x86_64-w64-mingw32.cmake +0 -9
  232. data/vendor/libressl/ssl/CMakeLists.txt +0 -183
  233. data/vendor/libressl/ssl/Makefile.am +0 -187
  234. data/vendor/libressl/tests/CMakeLists.txt +0 -970
  235. data/vendor/libressl/tests/Makefile.am +0 -944
  236. data/vendor/libressl/tests/aeadtest.sh +0 -30
  237. data/vendor/libressl/tests/arc4randomforktest.sh +0 -21
  238. data/vendor/libressl/tests/asn1time_small.test +0 -10
  239. data/vendor/libressl/tests/cmake/CMakeLists.txt +0 -52
  240. data/vendor/libressl/tests/cmake/crypto.c +0 -7
  241. data/vendor/libressl/tests/cmake/ssl.c +0 -6
  242. data/vendor/libressl/tests/cmake/tls.c +0 -6
  243. data/vendor/libressl/tests/compat/pipe2.c +0 -186
  244. data/vendor/libressl/tests/dtlstest.sh +0 -28
  245. data/vendor/libressl/tests/evptest.sh +0 -22
  246. data/vendor/libressl/tests/keypairtest.sh +0 -27
  247. data/vendor/libressl/tests/mlkem_tests.sh +0 -39
  248. data/vendor/libressl/tests/ocsptest.bat +0 -25
  249. data/vendor/libressl/tests/ocsptest.sh +0 -23
  250. data/vendor/libressl/tests/openssl.cnf +0 -29
  251. data/vendor/libressl/tests/optionstest.c +0 -381
  252. data/vendor/libressl/tests/pidwraptest.c +0 -85
  253. data/vendor/libressl/tests/pidwraptest.sh +0 -26
  254. data/vendor/libressl/tests/quictest.bat +0 -27
  255. data/vendor/libressl/tests/quictest.sh +0 -30
  256. data/vendor/libressl/tests/renegotiation_test.bat +0 -27
  257. data/vendor/libressl/tests/renegotiation_test.sh +0 -30
  258. data/vendor/libressl/tests/rfc5280time_small.test +0 -10
  259. data/vendor/libressl/tests/servertest.bat +0 -27
  260. data/vendor/libressl/tests/servertest.sh +0 -30
  261. data/vendor/libressl/tests/shutdowntest.bat +0 -27
  262. data/vendor/libressl/tests/shutdowntest.sh +0 -30
  263. data/vendor/libressl/tests/ssltest.bat +0 -32
  264. data/vendor/libressl/tests/ssltest.sh +0 -48
  265. data/vendor/libressl/tests/testdsa.bat +0 -47
  266. data/vendor/libressl/tests/testdsa.sh +0 -57
  267. data/vendor/libressl/tests/testenc.bat +0 -85
  268. data/vendor/libressl/tests/testenc.sh +0 -93
  269. data/vendor/libressl/tests/testrsa.bat +0 -47
  270. data/vendor/libressl/tests/testrsa.sh +0 -57
  271. data/vendor/libressl/tests/testssl.bat +0 -171
  272. data/vendor/libressl/tests/tlstest.bat +0 -27
  273. data/vendor/libressl/tests/tlstest.sh +0 -28
  274. data/vendor/libressl/tls/CMakeLists.txt +0 -125
  275. data/vendor/libressl/tls/Makefile.am +0 -76
  276. data/vendor/libressl/tls/compat/ftruncate.c +0 -17
  277. data/vendor/libressl/tls/compat/pread.c +0 -29
  278. data/vendor/libressl/tls/compat/pwrite.c +0 -29
  279. data/vendor/libressl/update.sh +0 -460
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 258d087be861df468fe7b4cf1628fe2669f095c0f347db65a5b5d63b31d34421
4
- data.tar.gz: 12d340eb8c71147557af11dbbeb2b961ec163ecdc4f28c8688d19a3b20e232ec
3
+ metadata.gz: ba017ea6da0eb880e2366157a7f1ff6ea936d32816ed2be45e3b3ca96b3c7408
4
+ data.tar.gz: 22f556023080078623fd8618122fe6707fa4cfdf6654b6c43e122f524c91c645
5
5
  SHA512:
6
- metadata.gz: e22dd92400e845b2b6931f10d96b5e926b413d72cc7e2c80df355f188b1989774fc963cbea32b9ca3373da403efc685ad3806e7a9965459295973db38f922d78
7
- data.tar.gz: e7fa4a7728f64306d2219225d41ab697e17bdd76274015c13ad16d9b2f14d1d6e961896e62c03ded86607f8b59e908ccac708b470a07be6f4aadac11349db034
6
+ metadata.gz: c08b772dd22791c297fc38dab3e8a73453ace1b54ee5c9e7b31ef127598987efd37fdfebf009b9202d3b550674006caf380a0e20fb50e4c2a3bee7e302afa97b
7
+ data.tar.gz: 1f35f727b26808a0bca4c24c19ee41ef4400766ec1b3b7033dbd39fea635168f93757825c4bf9e38d065c66652cd5120ce1880f8ef484233abdaa8bd9dff3b84
data/.gitignore CHANGED
@@ -37,6 +37,7 @@ build-iPhoneSimulator/
37
37
  /_yardoc/
38
38
  /doc/
39
39
  /rdoc/
40
+ /yard/
40
41
 
41
42
  ## Environment normalization:
42
43
  /.bundle/
data/.gitmodules CHANGED
@@ -2,6 +2,3 @@
2
2
  path = vendor/liburing
3
3
  url = https://github.com/axboe/liburing
4
4
 
5
- [submodule "vendor/libressl"]
6
- path = vendor/libressl
7
- url = https://github.com/libressl/portable
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.26.0 2026-02-13
2
+
3
+ - Reimplement um_op lifecycle tracking
4
+
5
+ # 0.25.0 2026-02-10
6
+
7
+ - Add Yard docs
8
+ - Add `UM::STDIN_FILENO`, `UM::STDOUT_FILENO`, `UM::STDERR_FILENO` constants
9
+ - Mark fibers as non-blocking in `#spin`
10
+ - Use `Set` instead of `Hash` for holding machine's fiber list
11
+ - Add inotify API, `#file_watch` convenience method
12
+ - Add SSL functionality: `#ssl_set_bio`, `#ssl_read`, `#ssl_write`
13
+
1
14
  # 0.24.0 2026-01-30
2
15
 
3
16
  - Add `Stream.resp_encode_cmd`
data/Gemfile CHANGED
@@ -1,3 +1,14 @@
1
1
  source 'https://gem.coop'
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem 'localhost'
7
+ gem 'rake-compiler', '~>1.3.0'
8
+ gem 'minitest', '~>6.0.1'
9
+ gem 'benchmark'
10
+ gem 'benchmark-ips'
11
+ gem 'http_parser.rb', '~>0.8.0'
12
+ gem 'yard'
13
+ gem 'irb'
14
+ end
data/README.md CHANGED
@@ -1,139 +1,293 @@
1
- # UringMachine
2
-
3
- <a href="http://rubygems.org/gems/uringmachine">
4
- <img src="https://badge.fury.io/rb/uringmachine.svg" alt="Ruby gem">
5
- </a>
6
- <a href="https://github.com/digital-fabric/uringmachine/actions?query=workflow%3ATests">
7
- <img src="https://github.com/digital-fabric/uringmachine/workflows/Tests/badge.svg" alt="Tests">
8
- </a>
9
- <a href="https://github.com/digital-fabric/uringmachine/blob/master/LICENSE">
10
- <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
11
- </a>
12
-
13
- UringMachine is a fiber-based library for creating concurrent apps in Ruby on
14
- modern Linux machines. UringMachine provides a rich API for performing I/O using
15
- [io_uring](https://en.wikipedia.org/wiki/Io_uring).
1
+ <h1 align="center">
2
+ <br>
3
+ UringMachine
4
+ </h1>
5
+
6
+ <h4 align="center">Ruby on io_uring!</h4>
7
+
8
+ <p align="center">
9
+ <a href="http://rubygems.org/gems/uringmachine">
10
+ <img src="https://badge.fury.io/rb/uringmachine.svg" alt="Ruby gem">
11
+ </a>
12
+ <a href="https://github.com/digital-fabric/uringmachine/actions">
13
+ <img src="https://github.com/digital-fabric/uringmachine/actions/workflows/test.yml/badge.svg" alt="Tests">
14
+ </a>
15
+ <a href="https://github.com/digital-fabric/uringmachine/blob/master/LICENSE">
16
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
17
+ </a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://www.rubydoc.info/gems/uringmachine">API reference</a>
22
+ </p>
23
+
24
+ ## What is UringMachine?
25
+
26
+ UringMachine is a Ruby gem for building fiber-based concurrent Ruby apps running
27
+ on Linux and using io_uring for performing I/O. UringMachine provides a
28
+ low-level API for performing concurrent I/O, as well as a full-featured
29
+ [`Fiber::Scheduler`](https://docs.ruby-lang.org/en/master/Fiber/Scheduler.html)
30
+ implementation that allows integration with the entire Ruby ecosystem.
16
31
 
17
32
  ## Features
18
33
 
19
- - Automatic fiber switching when performing blocking operations.
34
+ - Automatic fiber switching when performing blocking I/O operations.
20
35
  - Automatic cancellation using of ongoing operations with Ruby exceptions.
21
36
  - General-purpose API for cancelling any operation on timeout.
22
- - High performance (needs to be proved).
23
- - (Eventually) I/O class with buffered reads and an intuitive API.
37
+ - Excellent performance characteristics for concurrent I/O-bound applications.
38
+ - `Fiber::Scheduler` implementation to automatically integrate with the Ruby
39
+ ecosystem in a transparent fashion.
24
40
 
25
41
  ## Design
26
42
 
27
- UringMachine is based on my experience marrying Ruby and io_uring:
28
-
29
- - [Polyphony](https://github.com/digital-fabric/polyphony) - a comprehensive gem
30
- providing io_uring functionality, structured concurrency, and monkey-patching
31
- for the Ruby standard library.
32
- - [IOU](https://github.com/digital-fabric/iou) - a low-level asynchronous API
33
- for using io_uring from Ruby.
34
-
35
- ### Learnings
36
-
37
- Some important learnings from those two projects, in no particular order:
38
-
39
- - Monkey-patching is not a good solution, long term. You need to deal with
40
- changing APIs (Ruby is evolving quite rapidly these days!), and anyways you're
41
- always going to get stuck with some standard Ruby API that's implemented as a
42
- C extension and just won't play nice with whatever you're trying to do.
43
- - The design of the Polyphony io_uring backend was an evolution of something
44
- that was originally based on libev as an event loop. In hindsight, adapting
45
- the design for how io_uring worked led to code that was too complex and even
46
- somewhat brittle.
47
- - IOU showed me that even if we embrace callbacks, the developer experience is
48
- substantially inferior to what you can do with a sequential coding style. Even
49
- just in terms of line count - with callbacks you end up with roughly double
50
- the number of lines of code.
51
- - Implementing fiber switching on top of IOU was disappointing in terms of
52
- performance. In order for a fiber-based solution to be performed it had to be
53
- baked in - hence UringMachine.
54
- - Working with fibers has the very important benefit that you can keep stuff on
55
- the stack, instead of passing around all kinds of references to the heap. In
56
- addition, you mostly don't need to worry about marking Ruby objects used in
57
- operations, since normally they'll already be on the stack as method call
58
- parameters.
59
- - Polyphony was designed as an all-in-one solution that did everything: turning
60
- stock APIs into fiber-aware ones, providing a solid structured-concurrency
61
- implementation for controlling fiber life times, extensions providing
62
- additional features such as compressing streaming data between two fds, other
63
- APIs based on splicing etc. Perhaps a more cautious approach would be better.
64
- - Pending operation lifetime management in Polyphony was based a complex
65
- reference counting scheme that proved problematic, especially for multishot
66
- operations.
67
-
68
- So, based on those two projects, I wanted to design a Ruby API for io_uring
69
- based on the following principles:
70
-
71
- - Automatic fiber switching.
72
- - No monkey-patching. Instead, provide a simple custom API, as a replacement for
73
- the stock Ruby `IO` and `Socket` classes.
74
- - Simpler management of pending operation lifetime.
75
- - Do not insist on structured concurrency, just provide the APIs necessary to
76
- create actors and to supervise the execution of fibers.
77
-
78
- ### Cancellation
79
-
80
- When working with io_uring, managing the life cycle of asynchronous operations
81
- is quite tricky, especially with regards to cancellation. This is due to the
82
- fact each operation lives on both sides of the userspace-kernel divide. This
83
- means that when cancelling an operation, we cannot free, or dispose of any
84
- resources associated with the operation, until we know for sure that the kernel
85
- side is also done with the operation.
86
-
87
- As stated above, working with fibers allows us to keep operation metadata and
88
- associated data (such as buffers etc) on the stack, which can greatly simplify
89
- the managing of the operation's lifetime, as well as significantly reduce heap
90
- allocations.
91
-
92
- When a cancellation does occur, UringMachine issues a cancellation (using
93
- `io_uring_prep_cancel64`), and then waits for the corresponding CQE (with a
94
- `-ECANCELED` result).
95
-
96
- ## Short Example
43
+ In UringMachine, an I/O operation is performed by submitting it to the io_uring
44
+ interface (using the io_uring submission queue, or SQ) and waiting for a
45
+ corresponding entry to be added to the completion queue, or CQ. Since
46
+ UringMachine implements fiber-based concurrency, the fiber that performs the I/O
47
+ yields control after submitting the operation, and when a completion is
48
+ received, the fiber is scheduled to be resumed by putting it on the so-called
49
+ "runqueue", which is a queue of fibers ready to be resumed. When the runqueue is
50
+ exhausted, UringMachine enters the kernel in order to wait for one or more I/O
51
+ operation completions.
97
52
 
98
- ```ruby
99
- require 'uringmachine'
53
+ As a general rule, a single UringMachine instance is used for per thread,
54
+ managing the switching between the different fibers created on that same thread.
55
+ In addition, a UringMachine-based fiber scheduler may be installed in order to
56
+ allow any library that performs I/O using standard-library classes such as `IO`,
57
+ `TCPSocket` or `OpenSSL::SSL::SSLSocket`, and higher-level abstractions such as
58
+ `Net::HTTP` to perform I/O using UringMachine.
100
59
 
60
+ ## Getting Started
61
+
62
+ To install UringMachine, simply run `gem install uringmachine` or `bundle add
63
+ uringmachine` in your project directory. Note: to use UringMachine, you'll need
64
+ a Linux machine with a minimum kernel version of 6.7. Some features require
65
+ newer kernel versions.
66
+
67
+ To perform I/O using UringMachine, simply create an instance:
68
+
69
+ ```ruby
101
70
  machine = UringMachine.new
102
- stdout_fd = STDOUT.fileno
103
- stdin_fd = STDIN.fileno
104
- machine.write(stdout_fd, "Hello, world!\n")
105
71
 
106
- loop do
107
- machine.write(stdout_fd, "Say something: ")
72
+ # or alternatively
73
+ machine = UM.new
74
+ ```
75
+
76
+ You can perform I/O by directly making method calls such as `write` or `read`
77
+ (for the full API see the reference.):
78
+
79
+ ```ruby
80
+ # Most UringMachine instance methods will need you to provide a file descriptor.
81
+ # Here we print a message to STDOUT. Note the explicit line break:
82
+ machine.write(STDOUT, "Hello, world!\n")
83
+ ```
84
+
85
+ UringMachine provides an I/O interface that is to a large degree equivalent to
86
+ the Unix standard C interface:
87
+
88
+ ```ruby
89
+ # Constants used for the different I/O APIs are available under the
90
+ # UringMachine, or UM namespace.
91
+ fd = machine.open('foo.txt', UM::O_RDONLY)
92
+ buf = +''
93
+ size = machine.read(fd, buf, 8192)
94
+ machine.write(STDOUT, "File content: #{buf.inspect}")
95
+ machine.close(fd)
96
+
97
+ # Or alternatively (with automatic file closing):
98
+ machine.open('foo.txt', UM::O_RDONLY) do |fd|
99
+ buf = +''
100
+ size = machine.read(fd, buf, 8192)
101
+ machine.write(STDOUT, "File content: #{buf.inspect}")
102
+ end
103
+ ```
104
+
105
+ ## Fiber control
106
+
107
+ To perform I/O operations concurrently, you can spin up new fibers by calling
108
+ `#spin`. You can wait for a fiber to terminate by calling `#join`:
109
+
110
+ ```ruby
111
+ # This creates a pipe and returns the file descriptors for its read and write
112
+ # ends.
113
+ r_fd, w_fd = UM.pipe
114
+
115
+ # read from pipe
116
+ read_fiber = machine.spin do
108
117
  buf = +''
109
- res = machine.read(stdin_fd, buf, 8192)
110
- if res > 0
111
- machine.write(stdout_fd, "You said: #{buf}")
112
- else
113
- break
118
+ loop do
119
+ len = machine.read(r_fd, buf, 8192)
120
+ break if len == 0
121
+
122
+ # print to STDOUT
123
+ machine.write(UM::STDOUT_FILENO, "#{buf}\n")
114
124
  end
115
125
  end
126
+
127
+ write_fiber = machine.spin do
128
+ (1..10).each do |count|
129
+ machine.sleep(1)
130
+ machine.write(w_fd, "#{count} Mississipi")
131
+ end
132
+ machine.close(w_fd)
133
+ end
134
+
135
+ # Wait for both fibers to finish running
136
+ machine.join(read_fiber, write_fiber)
116
137
  ```
117
138
 
118
- ## Concurrent Execution
139
+ You can also terminate a fiber by scheduling it manually. Normally this would be
140
+ done using an exception, which would cause the fiber to cancel whatever
141
+ operation it is currently waiting for, and run any `rescue` or `ensure` block:
119
142
 
120
- Concurrent execution is done by calling `#spin`, which creates a fiber:
143
+ ```ruby
144
+ sleep_fiber = machine.spin do
145
+ puts "Going to sleep..."
146
+ machine.sleep(3)
147
+ puts "Done sleeping."
148
+ rescue => e
149
+ puts "Got error: #{e.inspect}"
150
+ end
151
+
152
+ # Let sleep_fiber start running
153
+ machine.sleep(0.1)
154
+ machine.schedule(sleep_fiber, RuntimeError.new('Cancel!'))
155
+ machine.join(sleep_fiber)
156
+ ```
157
+
158
+ ## Synchronization primitives
159
+
160
+ UringMachine also includes a io_uring-based implementation of a queue and a
161
+ mutex. `UM::Mutex` can be used for synchronizing access to a protected resource,
162
+ for example a database connection:
121
163
 
122
164
  ```ruby
123
- machine = UringMachine.new
165
+ class DBConnection
166
+ def initialize(machine, db)
167
+ @machine = machine
168
+ @db = db
169
+ @mutex = UM::Mutex.new
170
+ end
171
+
172
+ def query(sql)
173
+ @machine.synchronize(@mutex) do
174
+ @db.query(sql)
175
+ end
176
+ end
177
+ ```
178
+
179
+ A queue can be used to coordinate between multiple fibers, for example a fiber
180
+ that consumes data and a fiber that produces data:
124
181
 
125
- rfd, wfd = machine.pipe
182
+ ```ruby
183
+ queue = UM::Queue.new
126
184
 
127
- f1 = machine.spin do
128
- machine.write(wfd, 'hello')
129
- machine.write(wfd, 'world')
130
- machine.close(wfd)
185
+ producer = machine.spin do
186
+ 10.times do
187
+ # lazy producer wants to sleep a bit
188
+ machine.sleep(rand(0.1..1.5))
189
+ machine.push(queue, rand(1000))
190
+ end
191
+ machine.push(queue, :STOP)
131
192
  end
132
193
 
133
- bgid = machine.setup_buffer_ring(4096, 1024)
134
- f2 = machine.spin do
135
- machine.read_each(rfd, bgid) do |str|
136
- puts str
194
+ consumer = machine.spin do
195
+ sum = 0
196
+ loop do
197
+ # the call to #shift blocks if the queue is empty
198
+ value = machine.shift(queue)
199
+ break if value == :STOP
200
+
201
+ sum += value
137
202
  end
203
+ puts "Sum: #{sum}"
204
+ end
205
+
206
+ machine.join(producer, consumer)
207
+ ```
208
+
209
+ Note: to use the regular Ruby `Mutex` and `Queue` together with UringMachine,
210
+ you'll need to set up a fiber scheduler (see below).
211
+
212
+ ## Sleeping and Working with Timeouts
213
+
214
+ The `#sleep` method allows a fiber to sleep for a period of time:
215
+
216
+ ```ruby
217
+ puts "Sleeping"
218
+ machine.sleep(1)
219
+ puts "Done sleeping"
220
+ ```
221
+
222
+ You can also perform operations periodically by calling `#periodically`:
223
+
224
+ ```ruby
225
+ machine.spin do
226
+ # This runs an infinite loop, invoking the block every 200 msecs
227
+ machine.periodically(0.2) do
228
+ machine.write(fd, "Hello")
229
+ end
230
+ end
231
+ ```
232
+
233
+ UringMachine also provides a uniform API for implementing timeouts. To add a
234
+ timeout to an operation, you need to wrap it with a call to `#timeout`:
235
+
236
+ ```ruby
237
+ class TOError < StandardError; end;
238
+
239
+ # timeout after 3 seconds
240
+ machine.timeout(3, TOError) do
241
+ # wait to shift an item from the queue
242
+ value = machine.shift(queue)
243
+ rescue TOError
244
+ value = nil
138
245
  end
139
246
  ```
247
+
248
+ ## The Fiber Scheduler
249
+
250
+ In order to allow UringMachine to integrate with the rest of the Ruby ecosystem,
251
+ and act as the underlying I/O layer, UringMachine includes a full-featured
252
+ implementation of the `Fiber::Scheduler` interface.
253
+
254
+ Note: in order to benefit a the fiber scheduler you'll need to create so-called
255
+ "non-blocking" fibers. This is usually achieved automatically in app servers
256
+ such as [Falcon](https://github.com/socketry/falcon/).
257
+
258
+ To start a fiber scheduler, you need to create an instance of
259
+ `UringMachine::FiberScheduler` for each thread where you'll be doing fiber-based
260
+ concurrent I/O:
261
+
262
+ ```ruby
263
+ machine = UM.new
264
+ scheduler = UM::FiberScheduler.new(machine)
265
+ Fiber.set_scheduler(scheduler)
266
+ ```
267
+
268
+ Once the scheduler in place, it's going to handle any I/O or other blocking
269
+ operation (such as sleeping, or locking a mutex, or waiting for a thread to
270
+ terminate) by offloading the operations to the underlying UringMachine instance,
271
+ *provided the I/O is performed inside of a non-blocking fiber. A non-blocking
272
+ fiber can be started either using `UringMachine#spin` or alternatively,
273
+ `Fiber.schedule`:
274
+
275
+ ```ruby
276
+ machine = UM.new
277
+ scheduler = UM::FiberScheduler.new(machine)
278
+ Fiber.set_scheduler(scheduler)
279
+
280
+ fiber = Fiber.schedule do
281
+ # this will sleep using underlying UringMachine instance. It is equivalent to
282
+ # calling machine.sleep(0.05)
283
+ sleep(0.05)
284
+ end
285
+ ```
286
+
287
+ ## Performance
288
+
289
+ [Detailed benchmarks](benchmark/README.md)
290
+
291
+ ## API Reference
292
+
293
+ [API Reference](https://www.rubydoc.info/gems/uringmachine)
data/Rakefile CHANGED
@@ -37,3 +37,11 @@ task :release do
37
37
  puts "Cleaning up..."
38
38
  `rm *.gem`
39
39
  end
40
+
41
+ require 'yard'
42
+ YARD_FILES = FileList['ext/um/*.c', 'lib/uringmachine.rb', 'lib/uringmachine/**/*.rb']
43
+
44
+ YARD::Rake::YardocTask.new do |t|
45
+ t.files = YARD_FILES
46
+ t.options = %w( --verbose -o yard --readme README.md)
47
+ end
data/TODO.md CHANGED
@@ -1,27 +1,25 @@
1
1
  ## immediate
2
2
 
3
- - Fix all futex value (Queue, Mutex) to be aligned
3
+ - Fix all futex value (Queue, Mutex) to be properly aligned
4
4
 
5
- ## Sidecar thread
6
-
7
- The sidecar thread is an auxiliary thread that is used to wait for CQEs. It
8
- calls `io_uring_wait_cqe` (or equivalent lower-level interface) in a loop, and
9
- each time a CQE is available, it signals this to the primary UringMachine
10
- thread (using a futex).
5
+ ## Buffer rings - automatic management
11
6
 
12
- The primary UringMachine thread runs fibers from the runqueue. When the runqueue
13
- is exhausted, it performs a `io_uring_submit` for unsubmitted ops. It then waits
14
- for the futex to become signalled (non-zero), and then processes all available
15
- completions.
7
+ - Take the buffer_pool branch, rewrite it
8
+ - Allow multiple stream modes:
9
+ - :buffer_pool - uses buffer rings
10
+ - :ssl - read from an SSL connection (`SSLSocket`)
11
+ - :io - read from an `IO`
16
12
 
17
- ## Buffer rings - automatic management
13
+ The API will look something like:
18
14
 
19
15
  ```ruby
20
- # completely hands off
21
- machine.read_each(fd) { |str| ... }
16
+ # The mode is selected automatically according to the given target
22
17
 
23
- # what if we want to get IO::Buffer?
24
- machine.read_each(fd, io_buffer: true) { |iobuff, len| ... }
18
+ stream = UM::Stream.new(fd) # buffer_pool mode
19
+
20
+ stream = UM::Stream.new(ssl_sock) # ssl mode
21
+
22
+ stream = UM::Stream.new(conn) # io mode
25
23
  ```
26
24
 
27
25
  ## Balancing I/O with the runqueue
@@ -81,10 +79,6 @@ Op lifecycle management can be much much simpler
81
79
 
82
80
  ## ops
83
81
 
84
- - [ ] multishot timeout
85
- - [v] machine.periodically(interval) { ... }
86
- - [ ] machine.prep_timeout_multishot(interval)
87
-
88
82
  - splice / - tee
89
83
  - sendto
90
84
  - recvfrom
@@ -96,7 +90,6 @@ Op lifecycle management can be much much simpler
96
90
  - fadvise
97
91
  - madvise
98
92
  - getxattr / setxattr
99
- - send_bundle / recv_bundle (kernel >= 6.10)
100
93
 
101
94
  ## actors
102
95
 
data/benchmark/common.rb CHANGED
@@ -10,6 +10,8 @@ gemfile do
10
10
  gem 'async'
11
11
  gem 'pg'
12
12
  gem 'gvltools'
13
+ gem 'openssl'
14
+ gem 'localhost'
13
15
  end
14
16
 
15
17
  require 'uringmachine/fiber_scheduler'
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark'
9
+ gem 'benchmark-ips'
10
+ gem 'openssl'
11
+ gem 'localhost'
12
+ end
13
+
14
+ require 'uringmachine'
15
+ require 'benchmark/ips'
16
+ require 'openssl'
17
+ require 'localhost/authority'
18
+
19
+ authority = Localhost::Authority.fetch
20
+ ctx = authority.server_context
21
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
22
+
23
+ Socket.do_not_reverse_lookup = true
24
+ tcps = TCPServer.new("127.0.0.1", 0)
25
+ port = tcps.connect_address.ip_port
26
+
27
+ ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
28
+
29
+ Thread.new do
30
+ Thread.current.report_on_exception = false
31
+ loop do
32
+ begin
33
+ ssl = ssls.accept
34
+ rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL,
35
+ Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET
36
+ retry
37
+ end
38
+
39
+ Thread.new do
40
+ Thread.current.report_on_exception = false
41
+
42
+ begin
43
+ while line = ssl.gets
44
+ ssl.write(line)
45
+ end
46
+ ensure
47
+ ssl.close
48
+ end
49
+ true
50
+ end
51
+ end
52
+ end
53
+
54
+ @ssl_stock = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port)
55
+ @ssl_stock.sync_close = true
56
+ @ssl_stock.connect
57
+
58
+ um = UM.new
59
+
60
+ @ssl_um = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port)
61
+ @ssl_um.sync_close = true
62
+ um.ssl_set_bio(@ssl_um)
63
+ @ssl_um.connect
64
+
65
+ @msg = 'abc' * 1000
66
+
67
+ def do_io(ssl)
68
+ ssl.puts @msg
69
+ ssl.gets
70
+ end
71
+
72
+ Benchmark.ips do |x|
73
+ x.report('stock') { do_io(@ssl_stock) }
74
+ x.report('UM BIO') { do_io(@ssl_um) }
75
+
76
+ x.compare!(order: :baseline)
77
+ end