uringmachine 0.23.1 → 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 (291) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/.gitignore +1 -0
  4. data/.gitmodules +0 -3
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +12 -1
  7. data/README.md +266 -112
  8. data/Rakefile +8 -0
  9. data/TODO.md +40 -17
  10. data/benchmark/bm_io_pipe.rb +43 -1
  11. data/benchmark/bm_io_socketpair.rb +32 -2
  12. data/benchmark/bm_mutex_io.rb +47 -5
  13. data/benchmark/chart_bm_io_pipe_x.png +0 -0
  14. data/benchmark/common.rb +163 -17
  15. data/benchmark/http_parse.rb +9 -9
  16. data/benchmark/http_server_accept_queue.rb +104 -0
  17. data/benchmark/http_server_multi_accept.rb +93 -0
  18. data/benchmark/http_server_multi_ractor.rb +99 -0
  19. data/benchmark/http_server_single_thread.rb +80 -0
  20. data/benchmark/ips_io_pipe.rb +146 -0
  21. data/benchmark/openssl.rb +77 -0
  22. data/benchmark/openssl_socketpair.rb +112 -0
  23. data/benchmark/sqlite.rb +1 -1
  24. data/docs/design/buffer_pool.md +183 -0
  25. data/docs/um_api.md +91 -0
  26. data/examples/fiber_scheduler_file_io.rb +34 -0
  27. data/examples/fiber_scheduler_file_io_async.rb +33 -0
  28. data/ext/um/extconf.rb +15 -0
  29. data/ext/um/um.c +83 -50
  30. data/ext/um/um.h +18 -3
  31. data/ext/um/um_async_op_class.c +31 -0
  32. data/ext/um/um_class.c +759 -30
  33. data/ext/um/um_const.c +31 -0
  34. data/ext/um/um_mutex_class.c +12 -0
  35. data/ext/um/um_queue_class.c +16 -0
  36. data/ext/um/um_sidecar.c +106 -0
  37. data/ext/um/um_ssl.c +109 -0
  38. data/ext/um/um_stream.c +40 -8
  39. data/ext/um/um_stream_class.c +14 -0
  40. data/ext/um/um_utils.c +3 -4
  41. data/grant-2025/interim-report.md +130 -0
  42. data/grant-2025/journal.md +166 -2
  43. data/grant-2025/tasks.md +32 -20
  44. data/lib/uringmachine/dns_resolver.rb +38 -0
  45. data/lib/uringmachine/fiber_scheduler.rb +42 -32
  46. data/lib/uringmachine/version.rb +1 -1
  47. data/lib/uringmachine.rb +105 -7
  48. data/test/helper.rb +23 -3
  49. data/test/test_fiber.rb +16 -0
  50. data/test/test_fiber_scheduler.rb +221 -72
  51. data/test/test_ssl.rb +85 -0
  52. data/test/test_stream.rb +27 -0
  53. data/test/test_um.rb +250 -26
  54. data/uringmachine.gemspec +1 -7
  55. data/vendor/liburing/examples/send-zerocopy.c +43 -31
  56. data/vendor/liburing/examples/zcrx.c +260 -69
  57. data/vendor/liburing/liburing.spec +1 -1
  58. data/vendor/liburing/src/include/liburing/io_uring.h +12 -0
  59. data/vendor/liburing/src/include/liburing.h +3 -2
  60. data/vendor/liburing/src/liburing-ffi.map +4 -0
  61. data/vendor/liburing/src/liburing.map +4 -0
  62. data/vendor/liburing/src/queue.c +12 -0
  63. data/vendor/liburing/src/register.c +1 -0
  64. data/vendor/liburing/src/setup.c +15 -7
  65. data/vendor/liburing/test/Makefile +8 -4
  66. data/vendor/liburing/test/conn-unreach.c +1 -1
  67. data/vendor/liburing/test/epwait.c +32 -6
  68. data/vendor/liburing/test/io-wq-exit.c +131 -0
  69. data/vendor/liburing/test/iowait.c +1 -1
  70. data/vendor/liburing/test/min-timeout.c +3 -1
  71. data/vendor/liburing/test/open-close.c +39 -0
  72. data/vendor/liburing/test/poll-update-trigger.c +85 -0
  73. data/vendor/liburing/test/recvsend_bundle.c +14 -11
  74. data/vendor/liburing/test/sendzc-bug.c +146 -0
  75. data/vendor/liburing/test/sqe-mixed-nop.c +151 -7
  76. data/vendor/liburing/test/test.h +2 -0
  77. data/vendor/liburing/test/timestamp-bug.c +135 -0
  78. data/vendor/liburing/test/timestamp.c +5 -0
  79. data/vendor/liburing/test/vec-regbuf.c +136 -1
  80. metadata +50 -284
  81. data/vendor/libressl/.github/scripts/changelog.sh +0 -74
  82. data/vendor/libressl/.github/workflows/android.yml +0 -35
  83. data/vendor/libressl/.github/workflows/cifuzz.yml +0 -33
  84. data/vendor/libressl/.github/workflows/cmake-config.yml +0 -98
  85. data/vendor/libressl/.github/workflows/coverity.yml +0 -69
  86. data/vendor/libressl/.github/workflows/emscripten.yml +0 -71
  87. data/vendor/libressl/.github/workflows/fedora-rawhide.yml +0 -39
  88. data/vendor/libressl/.github/workflows/freebsd.yml +0 -71
  89. data/vendor/libressl/.github/workflows/linux.yml +0 -71
  90. data/vendor/libressl/.github/workflows/macos.yml +0 -37
  91. data/vendor/libressl/.github/workflows/release.yml +0 -81
  92. data/vendor/libressl/.github/workflows/rust-openssl.yml +0 -47
  93. data/vendor/libressl/.github/workflows/solaris.yml +0 -37
  94. data/vendor/libressl/.github/workflows/windows.yml +0 -70
  95. data/vendor/libressl/.gitignore +0 -333
  96. data/vendor/libressl/CMakeLists.txt +0 -581
  97. data/vendor/libressl/COPYING +0 -133
  98. data/vendor/libressl/ChangeLog +0 -3280
  99. data/vendor/libressl/FindLibreSSL.cmake +0 -232
  100. data/vendor/libressl/LibreSSLConfig.cmake.in +0 -36
  101. data/vendor/libressl/Makefile.am +0 -60
  102. data/vendor/libressl/Makefile.am.common +0 -20
  103. data/vendor/libressl/OPENBSD_BRANCH +0 -1
  104. data/vendor/libressl/README.md +0 -238
  105. data/vendor/libressl/README.mingw.md +0 -43
  106. data/vendor/libressl/apps/CMakeLists.txt +0 -18
  107. data/vendor/libressl/apps/Makefile.am +0 -5
  108. data/vendor/libressl/apps/nc/CMakeLists.txt +0 -67
  109. data/vendor/libressl/apps/nc/Makefile.am +0 -64
  110. data/vendor/libressl/apps/nc/compat/accept4.c +0 -17
  111. data/vendor/libressl/apps/nc/compat/readpassphrase.c +0 -205
  112. data/vendor/libressl/apps/nc/compat/socket.c +0 -29
  113. data/vendor/libressl/apps/nc/compat/sys/socket.h +0 -30
  114. data/vendor/libressl/apps/ocspcheck/CMakeLists.txt +0 -44
  115. data/vendor/libressl/apps/ocspcheck/Makefile.am +0 -45
  116. data/vendor/libressl/apps/ocspcheck/compat/.gitignore +0 -0
  117. data/vendor/libressl/apps/openssl/CMakeLists.txt +0 -97
  118. data/vendor/libressl/apps/openssl/Makefile.am +0 -108
  119. data/vendor/libressl/apps/openssl/apps_win.c +0 -138
  120. data/vendor/libressl/apps/openssl/certhash_win.c +0 -13
  121. data/vendor/libressl/apps/openssl/compat/clock_gettime_osx.c +0 -26
  122. data/vendor/libressl/apps/openssl/compat/poll_win.c +0 -329
  123. data/vendor/libressl/appveyor.yml +0 -53
  124. data/vendor/libressl/autogen.sh +0 -15
  125. data/vendor/libressl/check-release.sh +0 -86
  126. data/vendor/libressl/cmake_export_symbol.cmake +0 -71
  127. data/vendor/libressl/cmake_uninstall.cmake.in +0 -36
  128. data/vendor/libressl/config +0 -17
  129. data/vendor/libressl/configure.ac +0 -165
  130. data/vendor/libressl/crypto/CMakeLists.txt +0 -863
  131. data/vendor/libressl/crypto/Makefile.am +0 -962
  132. data/vendor/libressl/crypto/Makefile.am.arc4random +0 -46
  133. data/vendor/libressl/crypto/Makefile.am.elf-mips +0 -14
  134. data/vendor/libressl/crypto/Makefile.am.elf-mips64 +0 -14
  135. data/vendor/libressl/crypto/Makefile.am.elf-x86_64 +0 -35
  136. data/vendor/libressl/crypto/Makefile.am.macosx-x86_64 +0 -35
  137. data/vendor/libressl/crypto/Makefile.am.masm-x86_64 +0 -22
  138. data/vendor/libressl/crypto/Makefile.am.mingw64-x86_64 +0 -23
  139. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_darwin.c +0 -60
  140. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_linux.c +0 -62
  141. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_none.c +0 -26
  142. data/vendor/libressl/crypto/arch/aarch64/crypto_cpu_caps_windows.c +0 -36
  143. data/vendor/libressl/crypto/arch/loongarch64/crypto_arch.h +0 -21
  144. data/vendor/libressl/crypto/arch/mips/crypto_arch.h +0 -21
  145. data/vendor/libressl/crypto/bn/arch/loongarch64/bn_arch.h +0 -23
  146. data/vendor/libressl/crypto/bn/arch/mips/bn_arch.h +0 -24
  147. data/vendor/libressl/crypto/compat/.gitignore +0 -31
  148. data/vendor/libressl/crypto/compat/arc4random.h +0 -41
  149. data/vendor/libressl/crypto/compat/b_win.c +0 -55
  150. data/vendor/libressl/crypto/compat/bsd-asprintf.c +0 -96
  151. data/vendor/libressl/crypto/compat/crypto_lock_win.c +0 -56
  152. data/vendor/libressl/crypto/compat/explicit_bzero_win.c +0 -13
  153. data/vendor/libressl/crypto/compat/freezero.c +0 -32
  154. data/vendor/libressl/crypto/compat/getdelim.c +0 -78
  155. data/vendor/libressl/crypto/compat/getline.c +0 -40
  156. data/vendor/libressl/crypto/compat/getopt_long.c +0 -528
  157. data/vendor/libressl/crypto/compat/getpagesize.c +0 -18
  158. data/vendor/libressl/crypto/compat/getprogname_linux.c +0 -23
  159. data/vendor/libressl/crypto/compat/getprogname_unimpl.c +0 -7
  160. data/vendor/libressl/crypto/compat/getprogname_windows.c +0 -13
  161. data/vendor/libressl/crypto/compat/posix_win.c +0 -296
  162. data/vendor/libressl/crypto/compat/syslog_r.c +0 -19
  163. data/vendor/libressl/crypto/compat/ui_openssl_win.c +0 -334
  164. data/vendor/libressl/dist.sh +0 -22
  165. data/vendor/libressl/gen-coverage-report.sh +0 -58
  166. data/vendor/libressl/gen-openbsd-tags.sh +0 -20
  167. data/vendor/libressl/include/CMakeLists.txt +0 -61
  168. data/vendor/libressl/include/Makefile.am +0 -79
  169. data/vendor/libressl/include/arch/loongarch64/opensslconf.h +0 -150
  170. data/vendor/libressl/include/arch/mips/opensslconf.h +0 -150
  171. data/vendor/libressl/include/compat/arpa/inet.h +0 -15
  172. data/vendor/libressl/include/compat/arpa/nameser.h +0 -25
  173. data/vendor/libressl/include/compat/cet.h +0 -19
  174. data/vendor/libressl/include/compat/dirent.h +0 -17
  175. data/vendor/libressl/include/compat/dirent_msvc.h +0 -611
  176. data/vendor/libressl/include/compat/endian.h +0 -161
  177. data/vendor/libressl/include/compat/err.h +0 -95
  178. data/vendor/libressl/include/compat/fcntl.h +0 -32
  179. data/vendor/libressl/include/compat/getopt.h +0 -50
  180. data/vendor/libressl/include/compat/limits.h +0 -25
  181. data/vendor/libressl/include/compat/netdb.h +0 -10
  182. data/vendor/libressl/include/compat/netinet/in.h +0 -19
  183. data/vendor/libressl/include/compat/netinet/ip.h +0 -49
  184. data/vendor/libressl/include/compat/netinet/tcp.h +0 -10
  185. data/vendor/libressl/include/compat/poll.h +0 -63
  186. data/vendor/libressl/include/compat/pthread.h +0 -122
  187. data/vendor/libressl/include/compat/readpassphrase.h +0 -44
  188. data/vendor/libressl/include/compat/resolv.h +0 -24
  189. data/vendor/libressl/include/compat/stdint.h +0 -31
  190. data/vendor/libressl/include/compat/stdio.h +0 -65
  191. data/vendor/libressl/include/compat/stdlib.h +0 -57
  192. data/vendor/libressl/include/compat/string.h +0 -98
  193. data/vendor/libressl/include/compat/sys/_null.h +0 -18
  194. data/vendor/libressl/include/compat/sys/ioctl.h +0 -11
  195. data/vendor/libressl/include/compat/sys/mman.h +0 -19
  196. data/vendor/libressl/include/compat/sys/param.h +0 -15
  197. data/vendor/libressl/include/compat/sys/queue.h +0 -536
  198. data/vendor/libressl/include/compat/sys/select.h +0 -10
  199. data/vendor/libressl/include/compat/sys/socket.h +0 -18
  200. data/vendor/libressl/include/compat/sys/stat.h +0 -129
  201. data/vendor/libressl/include/compat/sys/time.h +0 -37
  202. data/vendor/libressl/include/compat/sys/tree.h +0 -1006
  203. data/vendor/libressl/include/compat/sys/types.h +0 -69
  204. data/vendor/libressl/include/compat/sys/uio.h +0 -17
  205. data/vendor/libressl/include/compat/syslog.h +0 -38
  206. data/vendor/libressl/include/compat/time.h +0 -59
  207. data/vendor/libressl/include/compat/unistd.h +0 -83
  208. data/vendor/libressl/include/compat/win32netcompat.h +0 -57
  209. data/vendor/libressl/include/openssl/Makefile.am.tpl +0 -45
  210. data/vendor/libressl/libcrypto.pc.in +0 -28
  211. data/vendor/libressl/libressl.pub +0 -2
  212. data/vendor/libressl/libssl.pc.in +0 -28
  213. data/vendor/libressl/libtls.pc.in +0 -28
  214. data/vendor/libressl/m4/ax_add_fortify_source.m4 +0 -80
  215. data/vendor/libressl/m4/ax_check_compile_flag.m4 +0 -53
  216. data/vendor/libressl/m4/check-hardening-options.m4 +0 -110
  217. data/vendor/libressl/m4/check-libc.m4 +0 -189
  218. data/vendor/libressl/m4/check-os-options.m4 +0 -181
  219. data/vendor/libressl/m4/disable-compiler-warnings.m4 +0 -44
  220. data/vendor/libressl/man/CMakeLists.txt +0 -26
  221. data/vendor/libressl/man/links +0 -2780
  222. data/vendor/libressl/man/update_links.sh +0 -25
  223. data/vendor/libressl/openssl.pc.in +0 -11
  224. data/vendor/libressl/patches/bn_shift.patch +0 -34
  225. data/vendor/libressl/patches/crypto_arch.h.patch +0 -34
  226. data/vendor/libressl/patches/crypto_namespace.h.patch +0 -22
  227. data/vendor/libressl/patches/netcat.c.patch +0 -178
  228. data/vendor/libressl/patches/openssl.c.patch +0 -12
  229. data/vendor/libressl/patches/opensslfeatures.h.patch +0 -49
  230. data/vendor/libressl/patches/patch-amd64-crypto-cpu-caps.c.patch +0 -20
  231. data/vendor/libressl/patches/patch-i386-crypto-cpu-caps.c.patch +0 -20
  232. data/vendor/libressl/patches/speed.c.patch +0 -114
  233. data/vendor/libressl/patches/ssl_namespace.h.patch +0 -21
  234. data/vendor/libressl/patches/tls.h.patch +0 -16
  235. data/vendor/libressl/patches/tls_config.c.patch +0 -15
  236. data/vendor/libressl/patches/win32_amd64_bn_arch.h.patch +0 -28
  237. data/vendor/libressl/patches/windows_headers.patch +0 -80
  238. data/vendor/libressl/scripts/config.guess +0 -1774
  239. data/vendor/libressl/scripts/config.sub +0 -1907
  240. data/vendor/libressl/scripts/i686-w64-mingw32.cmake +0 -9
  241. data/vendor/libressl/scripts/test +0 -210
  242. data/vendor/libressl/scripts/wrap-compiler-for-flag-check +0 -31
  243. data/vendor/libressl/scripts/x86_64-w64-mingw32.cmake +0 -9
  244. data/vendor/libressl/ssl/CMakeLists.txt +0 -183
  245. data/vendor/libressl/ssl/Makefile.am +0 -187
  246. data/vendor/libressl/tests/CMakeLists.txt +0 -970
  247. data/vendor/libressl/tests/Makefile.am +0 -944
  248. data/vendor/libressl/tests/aeadtest.sh +0 -30
  249. data/vendor/libressl/tests/arc4randomforktest.sh +0 -21
  250. data/vendor/libressl/tests/asn1time_small.test +0 -10
  251. data/vendor/libressl/tests/cmake/CMakeLists.txt +0 -52
  252. data/vendor/libressl/tests/cmake/crypto.c +0 -7
  253. data/vendor/libressl/tests/cmake/ssl.c +0 -6
  254. data/vendor/libressl/tests/cmake/tls.c +0 -6
  255. data/vendor/libressl/tests/compat/pipe2.c +0 -186
  256. data/vendor/libressl/tests/dtlstest.sh +0 -28
  257. data/vendor/libressl/tests/evptest.sh +0 -22
  258. data/vendor/libressl/tests/keypairtest.sh +0 -27
  259. data/vendor/libressl/tests/mlkem_tests.sh +0 -39
  260. data/vendor/libressl/tests/ocsptest.bat +0 -25
  261. data/vendor/libressl/tests/ocsptest.sh +0 -23
  262. data/vendor/libressl/tests/openssl.cnf +0 -29
  263. data/vendor/libressl/tests/optionstest.c +0 -381
  264. data/vendor/libressl/tests/pidwraptest.c +0 -85
  265. data/vendor/libressl/tests/pidwraptest.sh +0 -26
  266. data/vendor/libressl/tests/quictest.bat +0 -27
  267. data/vendor/libressl/tests/quictest.sh +0 -30
  268. data/vendor/libressl/tests/renegotiation_test.bat +0 -27
  269. data/vendor/libressl/tests/renegotiation_test.sh +0 -30
  270. data/vendor/libressl/tests/rfc5280time_small.test +0 -10
  271. data/vendor/libressl/tests/servertest.bat +0 -27
  272. data/vendor/libressl/tests/servertest.sh +0 -30
  273. data/vendor/libressl/tests/shutdowntest.bat +0 -27
  274. data/vendor/libressl/tests/shutdowntest.sh +0 -30
  275. data/vendor/libressl/tests/ssltest.bat +0 -32
  276. data/vendor/libressl/tests/ssltest.sh +0 -48
  277. data/vendor/libressl/tests/testdsa.bat +0 -47
  278. data/vendor/libressl/tests/testdsa.sh +0 -57
  279. data/vendor/libressl/tests/testenc.bat +0 -85
  280. data/vendor/libressl/tests/testenc.sh +0 -93
  281. data/vendor/libressl/tests/testrsa.bat +0 -47
  282. data/vendor/libressl/tests/testrsa.sh +0 -57
  283. data/vendor/libressl/tests/testssl.bat +0 -171
  284. data/vendor/libressl/tests/tlstest.bat +0 -27
  285. data/vendor/libressl/tests/tlstest.sh +0 -28
  286. data/vendor/libressl/tls/CMakeLists.txt +0 -125
  287. data/vendor/libressl/tls/Makefile.am +0 -76
  288. data/vendor/libressl/tls/compat/ftruncate.c +0 -17
  289. data/vendor/libressl/tls/compat/pread.c +0 -29
  290. data/vendor/libressl/tls/compat/pwrite.c +0 -29
  291. data/vendor/libressl/update.sh +0 -460
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a53007580b3e3fc11ce53466d79afa6a68e8546966d080b73b45cea166843e12
4
- data.tar.gz: f86c71ea56e72fe9d0fbbd130191b97d1e411feee7398f4bb748eda561ea4732
3
+ metadata.gz: 162f747c9286305e5bba72e0ab5c3d0d89c9a0ff42e1480b4c247a05dab0c2f3
4
+ data.tar.gz: 41ebc4a248a204f898d6d4cc954405b441c533109f08cae2806a0204bd1726aa
5
5
  SHA512:
6
- metadata.gz: 8a414b66431041c2d738cf8b0a68c4a78a2ebb14ad900868e351dac35de930aa5d1fe6862d67dedb90ce19f935bd6808ff0948825362960153c6fe95a7c4fe82
7
- data.tar.gz: 688cab6262c1a83267f4d08909554dbb7bb2046bd55b2b1c5eb6e7b183b5487c2d14d681e44174f347dd5afea860b7d04e3db256477433cf46d3dc60a192ee77
6
+ metadata.gz: d99daa2b45df0c28f9d087e19ce92678f78b76c551d47a5c55d93188a881b347a3d87f49cb93dc10686c5c89726fbc97b40e656cbcb09becbd53eeccc790e435
7
+ data.tar.gz: ba9f0408ac9157e82dfd34595d73d5e924a58cf8db0ee76971c5675b44c89a1ace5219e569c3a21ceff6e51439d39814f9a1f2a0314a2506bbde9169679c6707
@@ -12,7 +12,7 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  os: [ubuntu-latest]
15
- ruby: ['head']
15
+ ruby: ['4.0', 'head']
16
16
 
17
17
  name: ${{matrix.os}}, ${{matrix.ruby}}
18
18
 
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,20 @@
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
+
10
+ # 0.24.0 2026-01-30
11
+
12
+ - Add `Stream.resp_encode_cmd`
13
+ - Add sidecar mode
14
+ - Add test mode, remove special handling of OP_SCHEDULE in um_switch, do it only
15
+ in test mode
16
+ - Improve fiber scheduler error handling, add tests for I/O errors
17
+
1
18
  # 0.23.1 2025-12-16
2
19
 
3
20
  - Add `MSG_NOSIGNAL` to default flags for `#sendv` and `#send_bundle`
data/Gemfile CHANGED
@@ -1,3 +1,14 @@
1
- source 'https://rubygems.org'
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,6 +1,8 @@
1
1
  ## immediate
2
2
 
3
- ## buffer rings - automatic management
3
+ - Fix all futex value (Queue, Mutex) to be aligned
4
+
5
+ ## Buffer rings - automatic management
4
6
 
5
7
  ```ruby
6
8
  # completely hands off
@@ -10,40 +12,62 @@ machine.read_each(fd) { |str| ... }
10
12
  machine.read_each(fd, io_buffer: true) { |iobuff, len| ... }
11
13
  ```
12
14
 
13
- ## write/send multiple buffers at once
14
-
15
- This is done as vectored IO:
15
+ ## Balancing I/O with the runqueue
16
16
 
17
- ```ruby
18
- machine.writev(fd, buf1, buf2, buf3)
17
+ - in some cases where there are many entries in the runqueue, this can
18
+ negatively affect latency. In some cases, this can also lead to I/O
19
+ starvation. If the runqueue is never empty, then SQEs are not submitted and
20
+ CQEs are not processed.
21
+ - So we want to limit the number of consecutive fiber switches before processing
22
+ I/O.
23
+ - Some possible approaches:
19
24
 
20
- # with optional file offset:
21
- machine.writev(fd, buf1, buf2, buf3, 0)
25
+ 1. limit consecutive switches with a parameter
26
+ 2. limit consecutive switches relative to the runqueue size and/or the amount
27
+ of pending SQEs
28
+ 3. an adaptive algorithm that occasionally measures the time between I/O
29
+ processing iterations, and adjusts the consecutive switches limit?
22
30
 
23
- # for the moment it won't take flags
24
- machine.sendv(fd, buf1, buf2, buf3)
25
- ```
31
+ - We also want to devise some benchmark that measures throughput / latency with
32
+ different settings, in a situation with very high concurrency.
26
33
 
27
34
  ## useful concurrency tools
28
35
 
29
36
  - debounce
30
37
 
31
38
  ```ruby
32
- debouncer = UM.debounce { }
39
+ debouncer = machine.debounce { }
33
40
  ```
34
41
 
42
+ - read multiple files
35
43
 
44
+ ```ruby
45
+ # with a block
46
+ machine.read_files(*fns) { |fn, data| ... }
47
+
48
+ # without a block
49
+ machine.read_files(*fns) #=> { fn1:, fn2:, fn3:, ...}
50
+ ```
36
51
 
37
52
  ## polyvalent select
38
53
 
39
54
  - select on multiple queues (ala Go)
40
55
  - select on mixture of queues and fds
41
56
 
42
- ## ops
57
+ (see also simplified op management below)
58
+
59
+ ## simplified op management
60
+
61
+ Op lifecycle management can be much much simpler
62
+
63
+ - make all ops heap-allocated
64
+ - clear up state transitions:
43
65
 
44
- - [ ] multishot timeout
45
- - [v] machine.periodically(interval) { ... }
46
- - [ ] machine.prep_timeout_multishot(interval)
66
+ - kernel-side state: unsubmitted, submitted, completed, done (for multishot ops)
67
+ - app-side state: unsubmitted, submitted, ...
68
+
69
+
70
+ ## ops
47
71
 
48
72
  - splice / - tee
49
73
  - sendto
@@ -56,7 +80,6 @@ machine.sendv(fd, buf1, buf2, buf3)
56
80
  - fadvise
57
81
  - madvise
58
82
  - getxattr / setxattr
59
- - send_bundle / recv_bundle (kernel >= 6.10)
60
83
 
61
84
  ## actors
62
85
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './common'
4
4
 
5
- GROUPS = 50
5
+ GROUPS = 48
6
6
  ITERATIONS = 10000
7
7
 
8
8
  SIZE = 1024
@@ -52,6 +52,18 @@ class UMBenchmark
52
52
  end
53
53
  end
54
54
 
55
+ def do_baseline_um(machine)
56
+ GROUPS.times do
57
+ r, w = UM.pipe
58
+ ITERATIONS.times {
59
+ machine.write(w, DATA)
60
+ machine.read(r, +'', SIZE)
61
+ }
62
+ machine.close(w)
63
+ machine.close(r)
64
+ end
65
+ end
66
+
55
67
  def do_scheduler(scheduler, ios)
56
68
  GROUPS.times do
57
69
  r, w = IO.pipe
@@ -68,6 +80,22 @@ class UMBenchmark
68
80
  end
69
81
  end
70
82
 
83
+ def do_scheduler_x(div, scheduler, ios)
84
+ (GROUPS/div).times do
85
+ r, w = IO.pipe
86
+ r.sync = true
87
+ w.sync = true
88
+ Fiber.schedule do
89
+ ITERATIONS.times { w.write(DATA) }
90
+ w.close
91
+ end
92
+ Fiber.schedule do
93
+ ITERATIONS.times { r.readpartial(SIZE) }
94
+ r.close
95
+ end
96
+ end
97
+ end
98
+
71
99
  def do_um(machine, fibers, fds)
72
100
  GROUPS.times do
73
101
  r, w = UM.pipe
@@ -81,4 +109,18 @@ class UMBenchmark
81
109
  end
82
110
  end
83
111
  end
112
+
113
+ def do_um_x(div, machine, fibers, fds)
114
+ (GROUPS/div).times do
115
+ r, w = UM.pipe
116
+ fibers << machine.spin do
117
+ ITERATIONS.times { machine.write(w, DATA) }
118
+ machine.close_async(w)
119
+ end
120
+ fibers << machine.spin do
121
+ ITERATIONS.times { machine.read(r, +'', SIZE) }
122
+ machine.close_async(r)
123
+ end
124
+ end
125
+ end
84
126
  end