uringmachine 0.24.0 → 0.25.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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitmodules +0 -3
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +11 -0
  6. data/README.md +266 -112
  7. data/Rakefile +8 -0
  8. data/TODO.md +0 -17
  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/ext/um/extconf.rb +15 -0
  15. data/ext/um/um.c +18 -2
  16. data/ext/um/um.h +7 -2
  17. data/ext/um/um_async_op_class.c +31 -0
  18. data/ext/um/um_class.c +705 -19
  19. data/ext/um/um_const.c +31 -0
  20. data/ext/um/um_mutex_class.c +12 -0
  21. data/ext/um/um_queue_class.c +16 -0
  22. data/ext/um/um_ssl.c +109 -0
  23. data/ext/um/um_stream.c +9 -8
  24. data/ext/um/um_utils.c +3 -4
  25. data/grant-2025/interim-report.md +1 -1
  26. data/grant-2025/journal.md +4 -4
  27. data/grant-2025/tasks.md +6 -4
  28. data/lib/uringmachine/dns_resolver.rb +38 -0
  29. data/lib/uringmachine/fiber_scheduler.rb +7 -5
  30. data/lib/uringmachine/version.rb +1 -1
  31. data/lib/uringmachine.rb +106 -6
  32. data/test/helper.rb +15 -0
  33. data/test/test_fiber_scheduler.rb +37 -0
  34. data/test/test_ssl.rb +85 -0
  35. data/test/test_stream.rb +11 -0
  36. data/test/test_um.rb +156 -2
  37. data/uringmachine.gemspec +1 -7
  38. data/vendor/liburing/examples/send-zerocopy.c +43 -31
  39. data/vendor/liburing/examples/zcrx.c +260 -69
  40. data/vendor/liburing/liburing.spec +1 -1
  41. data/vendor/liburing/src/include/liburing/io_uring.h +12 -0
  42. data/vendor/liburing/src/include/liburing.h +3 -2
  43. data/vendor/liburing/src/liburing-ffi.map +4 -0
  44. data/vendor/liburing/src/liburing.map +4 -0
  45. data/vendor/liburing/src/queue.c +12 -0
  46. data/vendor/liburing/src/register.c +1 -0
  47. data/vendor/liburing/src/setup.c +15 -7
  48. data/vendor/liburing/test/Makefile +8 -4
  49. data/vendor/liburing/test/conn-unreach.c +1 -1
  50. data/vendor/liburing/test/epwait.c +32 -6
  51. data/vendor/liburing/test/io-wq-exit.c +131 -0
  52. data/vendor/liburing/test/iowait.c +1 -1
  53. data/vendor/liburing/test/min-timeout.c +3 -1
  54. data/vendor/liburing/test/open-close.c +39 -0
  55. data/vendor/liburing/test/poll-update-trigger.c +85 -0
  56. data/vendor/liburing/test/recvsend_bundle.c +14 -11
  57. data/vendor/liburing/test/sendzc-bug.c +146 -0
  58. data/vendor/liburing/test/sqe-mixed-nop.c +151 -7
  59. data/vendor/liburing/test/test.h +2 -0
  60. data/vendor/liburing/test/timestamp-bug.c +135 -0
  61. data/vendor/liburing/test/timestamp.c +5 -0
  62. data/vendor/liburing/test/vec-regbuf.c +136 -1
  63. metadata +37 -283
  64. data/vendor/libressl/.github/scripts/changelog.sh +0 -74
  65. data/vendor/libressl/.github/workflows/android.yml +0 -35
  66. data/vendor/libressl/.github/workflows/cifuzz.yml +0 -33
  67. data/vendor/libressl/.github/workflows/cmake-config.yml +0 -98
  68. data/vendor/libressl/.github/workflows/coverity.yml +0 -69
  69. data/vendor/libressl/.github/workflows/emscripten.yml +0 -71
  70. data/vendor/libressl/.github/workflows/fedora-rawhide.yml +0 -39
  71. data/vendor/libressl/.github/workflows/freebsd.yml +0 -71
  72. data/vendor/libressl/.github/workflows/linux.yml +0 -71
  73. data/vendor/libressl/.github/workflows/macos.yml +0 -37
  74. data/vendor/libressl/.github/workflows/release.yml +0 -81
  75. data/vendor/libressl/.github/workflows/rust-openssl.yml +0 -47
  76. data/vendor/libressl/.github/workflows/solaris.yml +0 -37
  77. data/vendor/libressl/.github/workflows/windows.yml +0 -70
  78. data/vendor/libressl/.gitignore +0 -333
  79. data/vendor/libressl/CMakeLists.txt +0 -581
  80. data/vendor/libressl/COPYING +0 -133
  81. data/vendor/libressl/ChangeLog +0 -3280
  82. data/vendor/libressl/FindLibreSSL.cmake +0 -232
  83. data/vendor/libressl/LibreSSLConfig.cmake.in +0 -36
  84. data/vendor/libressl/Makefile.am +0 -60
  85. data/vendor/libressl/Makefile.am.common +0 -20
  86. data/vendor/libressl/OPENBSD_BRANCH +0 -1
  87. data/vendor/libressl/README.md +0 -238
  88. data/vendor/libressl/README.mingw.md +0 -43
  89. data/vendor/libressl/apps/CMakeLists.txt +0 -18
  90. data/vendor/libressl/apps/Makefile.am +0 -5
  91. data/vendor/libressl/apps/nc/CMakeLists.txt +0 -67
  92. data/vendor/libressl/apps/nc/Makefile.am +0 -64
  93. data/vendor/libressl/apps/nc/compat/accept4.c +0 -17
  94. data/vendor/libressl/apps/nc/compat/readpassphrase.c +0 -205
  95. data/vendor/libressl/apps/nc/compat/socket.c +0 -29
  96. data/vendor/libressl/apps/nc/compat/sys/socket.h +0 -30
  97. data/vendor/libressl/apps/ocspcheck/CMakeLists.txt +0 -44
  98. data/vendor/libressl/apps/ocspcheck/Makefile.am +0 -45
  99. data/vendor/libressl/apps/ocspcheck/compat/.gitignore +0 -0
  100. data/vendor/libressl/apps/openssl/CMakeLists.txt +0 -97
  101. data/vendor/libressl/apps/openssl/Makefile.am +0 -108
  102. data/vendor/libressl/apps/openssl/apps_win.c +0 -138
  103. data/vendor/libressl/apps/openssl/certhash_win.c +0 -13
  104. data/vendor/libressl/apps/openssl/compat/clock_gettime_osx.c +0 -26
  105. data/vendor/libressl/apps/openssl/compat/poll_win.c +0 -329
  106. data/vendor/libressl/appveyor.yml +0 -53
  107. data/vendor/libressl/autogen.sh +0 -15
  108. data/vendor/libressl/check-release.sh +0 -86
  109. data/vendor/libressl/cmake_export_symbol.cmake +0 -71
  110. data/vendor/libressl/cmake_uninstall.cmake.in +0 -36
  111. data/vendor/libressl/config +0 -17
  112. data/vendor/libressl/configure.ac +0 -165
  113. data/vendor/libressl/crypto/CMakeLists.txt +0 -863
  114. data/vendor/libressl/crypto/Makefile.am +0 -962
  115. data/vendor/libressl/crypto/Makefile.am.arc4random +0 -46
  116. data/vendor/libressl/crypto/Makefile.am.elf-mips +0 -14
  117. data/vendor/libressl/crypto/Makefile.am.elf-mips64 +0 -14
  118. data/vendor/libressl/crypto/Makefile.am.elf-x86_64 +0 -35
  119. data/vendor/libressl/crypto/Makefile.am.macosx-x86_64 +0 -35
  120. data/vendor/libressl/crypto/Makefile.am.masm-x86_64 +0 -22
  121. data/vendor/libressl/crypto/Makefile.am.mingw64-x86_64 +0 -23
  122. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_darwin.c +0 -60
  123. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_linux.c +0 -62
  124. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_none.c +0 -26
  125. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_windows.c +0 -36
  126. data/vendor/libressl/crypto/arch/loongarch64/crypto_arch.h +0 -21
  127. data/vendor/libressl/crypto/arch/mips/crypto_arch.h +0 -21
  128. data/vendor/libressl/crypto/bn/arch/loongarch64/bn_arch.h +0 -23
  129. data/vendor/libressl/crypto/bn/arch/mips/bn_arch.h +0 -24
  130. data/vendor/libressl/crypto/compat/.gitignore +0 -31
  131. data/vendor/libressl/crypto/compat/arc4random.h +0 -41
  132. data/vendor/libressl/crypto/compat/b_win.c +0 -55
  133. data/vendor/libressl/crypto/compat/bsd-asprintf.c +0 -96
  134. data/vendor/libressl/crypto/compat/crypto_lock_win.c +0 -56
  135. data/vendor/libressl/crypto/compat/explicit_bzero_win.c +0 -13
  136. data/vendor/libressl/crypto/compat/freezero.c +0 -32
  137. data/vendor/libressl/crypto/compat/getdelim.c +0 -78
  138. data/vendor/libressl/crypto/compat/getline.c +0 -40
  139. data/vendor/libressl/crypto/compat/getopt_long.c +0 -528
  140. data/vendor/libressl/crypto/compat/getpagesize.c +0 -18
  141. data/vendor/libressl/crypto/compat/getprogname_linux.c +0 -23
  142. data/vendor/libressl/crypto/compat/getprogname_unimpl.c +0 -7
  143. data/vendor/libressl/crypto/compat/getprogname_windows.c +0 -13
  144. data/vendor/libressl/crypto/compat/posix_win.c +0 -296
  145. data/vendor/libressl/crypto/compat/syslog_r.c +0 -19
  146. data/vendor/libressl/crypto/compat/ui_openssl_win.c +0 -334
  147. data/vendor/libressl/dist.sh +0 -22
  148. data/vendor/libressl/gen-coverage-report.sh +0 -58
  149. data/vendor/libressl/gen-openbsd-tags.sh +0 -20
  150. data/vendor/libressl/include/CMakeLists.txt +0 -61
  151. data/vendor/libressl/include/Makefile.am +0 -79
  152. data/vendor/libressl/include/arch/loongarch64/opensslconf.h +0 -150
  153. data/vendor/libressl/include/arch/mips/opensslconf.h +0 -150
  154. data/vendor/libressl/include/compat/arpa/inet.h +0 -15
  155. data/vendor/libressl/include/compat/arpa/nameser.h +0 -25
  156. data/vendor/libressl/include/compat/cet.h +0 -19
  157. data/vendor/libressl/include/compat/dirent.h +0 -17
  158. data/vendor/libressl/include/compat/dirent_msvc.h +0 -611
  159. data/vendor/libressl/include/compat/endian.h +0 -161
  160. data/vendor/libressl/include/compat/err.h +0 -95
  161. data/vendor/libressl/include/compat/fcntl.h +0 -32
  162. data/vendor/libressl/include/compat/getopt.h +0 -50
  163. data/vendor/libressl/include/compat/limits.h +0 -25
  164. data/vendor/libressl/include/compat/netdb.h +0 -10
  165. data/vendor/libressl/include/compat/netinet/in.h +0 -19
  166. data/vendor/libressl/include/compat/netinet/ip.h +0 -49
  167. data/vendor/libressl/include/compat/netinet/tcp.h +0 -10
  168. data/vendor/libressl/include/compat/poll.h +0 -63
  169. data/vendor/libressl/include/compat/pthread.h +0 -122
  170. data/vendor/libressl/include/compat/readpassphrase.h +0 -44
  171. data/vendor/libressl/include/compat/resolv.h +0 -24
  172. data/vendor/libressl/include/compat/stdint.h +0 -31
  173. data/vendor/libressl/include/compat/stdio.h +0 -65
  174. data/vendor/libressl/include/compat/stdlib.h +0 -57
  175. data/vendor/libressl/include/compat/string.h +0 -98
  176. data/vendor/libressl/include/compat/sys/_null.h +0 -18
  177. data/vendor/libressl/include/compat/sys/ioctl.h +0 -11
  178. data/vendor/libressl/include/compat/sys/mman.h +0 -19
  179. data/vendor/libressl/include/compat/sys/param.h +0 -15
  180. data/vendor/libressl/include/compat/sys/queue.h +0 -536
  181. data/vendor/libressl/include/compat/sys/select.h +0 -10
  182. data/vendor/libressl/include/compat/sys/socket.h +0 -18
  183. data/vendor/libressl/include/compat/sys/stat.h +0 -129
  184. data/vendor/libressl/include/compat/sys/time.h +0 -37
  185. data/vendor/libressl/include/compat/sys/tree.h +0 -1006
  186. data/vendor/libressl/include/compat/sys/types.h +0 -69
  187. data/vendor/libressl/include/compat/sys/uio.h +0 -17
  188. data/vendor/libressl/include/compat/syslog.h +0 -38
  189. data/vendor/libressl/include/compat/time.h +0 -59
  190. data/vendor/libressl/include/compat/unistd.h +0 -83
  191. data/vendor/libressl/include/compat/win32netcompat.h +0 -57
  192. data/vendor/libressl/include/openssl/Makefile.am.tpl +0 -45
  193. data/vendor/libressl/libcrypto.pc.in +0 -28
  194. data/vendor/libressl/libressl.pub +0 -2
  195. data/vendor/libressl/libssl.pc.in +0 -28
  196. data/vendor/libressl/libtls.pc.in +0 -28
  197. data/vendor/libressl/m4/ax_add_fortify_source.m4 +0 -80
  198. data/vendor/libressl/m4/ax_check_compile_flag.m4 +0 -53
  199. data/vendor/libressl/m4/check-hardening-options.m4 +0 -110
  200. data/vendor/libressl/m4/check-libc.m4 +0 -189
  201. data/vendor/libressl/m4/check-os-options.m4 +0 -181
  202. data/vendor/libressl/m4/disable-compiler-warnings.m4 +0 -44
  203. data/vendor/libressl/man/CMakeLists.txt +0 -26
  204. data/vendor/libressl/man/links +0 -2780
  205. data/vendor/libressl/man/update_links.sh +0 -25
  206. data/vendor/libressl/openssl.pc.in +0 -11
  207. data/vendor/libressl/patches/bn_shift.patch +0 -34
  208. data/vendor/libressl/patches/crypto_arch.h.patch +0 -34
  209. data/vendor/libressl/patches/crypto_namespace.h.patch +0 -22
  210. data/vendor/libressl/patches/netcat.c.patch +0 -178
  211. data/vendor/libressl/patches/openssl.c.patch +0 -12
  212. data/vendor/libressl/patches/opensslfeatures.h.patch +0 -49
  213. data/vendor/libressl/patches/patch-amd64-crypto-cpu-caps.c.patch +0 -20
  214. data/vendor/libressl/patches/patch-i386-crypto-cpu-caps.c.patch +0 -20
  215. data/vendor/libressl/patches/speed.c.patch +0 -114
  216. data/vendor/libressl/patches/ssl_namespace.h.patch +0 -21
  217. data/vendor/libressl/patches/tls.h.patch +0 -16
  218. data/vendor/libressl/patches/tls_config.c.patch +0 -15
  219. data/vendor/libressl/patches/win32_amd64_bn_arch.h.patch +0 -28
  220. data/vendor/libressl/patches/windows_headers.patch +0 -80
  221. data/vendor/libressl/scripts/config.guess +0 -1774
  222. data/vendor/libressl/scripts/config.sub +0 -1907
  223. data/vendor/libressl/scripts/i686-w64-mingw32.cmake +0 -9
  224. data/vendor/libressl/scripts/test +0 -210
  225. data/vendor/libressl/scripts/wrap-compiler-for-flag-check +0 -31
  226. data/vendor/libressl/scripts/x86_64-w64-mingw32.cmake +0 -9
  227. data/vendor/libressl/ssl/CMakeLists.txt +0 -183
  228. data/vendor/libressl/ssl/Makefile.am +0 -187
  229. data/vendor/libressl/tests/CMakeLists.txt +0 -970
  230. data/vendor/libressl/tests/Makefile.am +0 -944
  231. data/vendor/libressl/tests/aeadtest.sh +0 -30
  232. data/vendor/libressl/tests/arc4randomforktest.sh +0 -21
  233. data/vendor/libressl/tests/asn1time_small.test +0 -10
  234. data/vendor/libressl/tests/cmake/CMakeLists.txt +0 -52
  235. data/vendor/libressl/tests/cmake/crypto.c +0 -7
  236. data/vendor/libressl/tests/cmake/ssl.c +0 -6
  237. data/vendor/libressl/tests/cmake/tls.c +0 -6
  238. data/vendor/libressl/tests/compat/pipe2.c +0 -186
  239. data/vendor/libressl/tests/dtlstest.sh +0 -28
  240. data/vendor/libressl/tests/evptest.sh +0 -22
  241. data/vendor/libressl/tests/keypairtest.sh +0 -27
  242. data/vendor/libressl/tests/mlkem_tests.sh +0 -39
  243. data/vendor/libressl/tests/ocsptest.bat +0 -25
  244. data/vendor/libressl/tests/ocsptest.sh +0 -23
  245. data/vendor/libressl/tests/openssl.cnf +0 -29
  246. data/vendor/libressl/tests/optionstest.c +0 -381
  247. data/vendor/libressl/tests/pidwraptest.c +0 -85
  248. data/vendor/libressl/tests/pidwraptest.sh +0 -26
  249. data/vendor/libressl/tests/quictest.bat +0 -27
  250. data/vendor/libressl/tests/quictest.sh +0 -30
  251. data/vendor/libressl/tests/renegotiation_test.bat +0 -27
  252. data/vendor/libressl/tests/renegotiation_test.sh +0 -30
  253. data/vendor/libressl/tests/rfc5280time_small.test +0 -10
  254. data/vendor/libressl/tests/servertest.bat +0 -27
  255. data/vendor/libressl/tests/servertest.sh +0 -30
  256. data/vendor/libressl/tests/shutdowntest.bat +0 -27
  257. data/vendor/libressl/tests/shutdowntest.sh +0 -30
  258. data/vendor/libressl/tests/ssltest.bat +0 -32
  259. data/vendor/libressl/tests/ssltest.sh +0 -48
  260. data/vendor/libressl/tests/testdsa.bat +0 -47
  261. data/vendor/libressl/tests/testdsa.sh +0 -57
  262. data/vendor/libressl/tests/testenc.bat +0 -85
  263. data/vendor/libressl/tests/testenc.sh +0 -93
  264. data/vendor/libressl/tests/testrsa.bat +0 -47
  265. data/vendor/libressl/tests/testrsa.sh +0 -57
  266. data/vendor/libressl/tests/testssl.bat +0 -171
  267. data/vendor/libressl/tests/tlstest.bat +0 -27
  268. data/vendor/libressl/tests/tlstest.sh +0 -28
  269. data/vendor/libressl/tls/CMakeLists.txt +0 -125
  270. data/vendor/libressl/tls/Makefile.am +0 -76
  271. data/vendor/libressl/tls/compat/ftruncate.c +0 -17
  272. data/vendor/libressl/tls/compat/pread.c +0 -29
  273. data/vendor/libressl/tls/compat/pwrite.c +0 -29
  274. 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: 162f747c9286305e5bba72e0ab5c3d0d89c9a0ff42e1480b4c247a05dab0c2f3
4
+ data.tar.gz: 41ebc4a248a204f898d6d4cc954405b441c533109f08cae2806a0204bd1726aa
5
5
  SHA512:
6
- metadata.gz: e22dd92400e845b2b6931f10d96b5e926b413d72cc7e2c80df355f188b1989774fc963cbea32b9ca3373da403efc685ad3806e7a9965459295973db38f922d78
7
- data.tar.gz: e7fa4a7728f64306d2219225d41ab697e17bdd76274015c13ad16d9b2f14d1d6e961896e62c03ded86607f8b59e908ccac708b470a07be6f4aadac11349db034
6
+ metadata.gz: d99daa2b45df0c28f9d087e19ce92678f78b76c551d47a5c55d93188a881b347a3d87f49cb93dc10686c5c89726fbc97b40e656cbcb09becbd53eeccc790e435
7
+ data.tar.gz: ba9f0408ac9157e82dfd34595d73d5e924a58cf8db0ee76971c5675b44c89a1ace5219e569c3a21ceff6e51439d39814f9a1f2a0314a2506bbde9169679c6707
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,12 @@
1
+ # 0.25.0 2026-02-10
2
+
3
+ - Add Yard docs
4
+ - Add `UM::STDIN_FILENO`, `UM::STDOUT_FILENO`, `UM::STDERR_FILENO` constants
5
+ - Mark fibers as non-blocking in `#spin`
6
+ - Use `Set` instead of `Hash` for holding machine's fiber list
7
+ - Add inotify API, `#file_watch` convenience method
8
+ - Add SSL functionality: `#ssl_set_bio`, `#ssl_read`, `#ssl_write`
9
+
1
10
  # 0.24.0 2026-01-30
2
11
 
3
12
  - 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
@@ -2,18 +2,6 @@
2
2
 
3
3
  - Fix all futex value (Queue, Mutex) to be 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).
11
-
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.
16
-
17
5
  ## Buffer rings - automatic management
18
6
 
19
7
  ```ruby
@@ -81,10 +69,6 @@ Op lifecycle management can be much much simpler
81
69
 
82
70
  ## ops
83
71
 
84
- - [ ] multishot timeout
85
- - [v] machine.periodically(interval) { ... }
86
- - [ ] machine.prep_timeout_multishot(interval)
87
-
88
72
  - splice / - tee
89
73
  - sendto
90
74
  - recvfrom
@@ -96,7 +80,6 @@ Op lifecycle management can be much much simpler
96
80
  - fadvise
97
81
  - madvise
98
82
  - getxattr / setxattr
99
- - send_bundle / recv_bundle (kernel >= 6.10)
100
83
 
101
84
  ## actors
102
85
 
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