uringmachine 0.3 → 0.5

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -1
  3. data/CHANGELOG.md +23 -0
  4. data/README.md +128 -0
  5. data/TODO.md +14 -0
  6. data/examples/bm_snooze.rb +89 -0
  7. data/examples/bm_write.rb +56 -0
  8. data/examples/dns_client.rb +12 -0
  9. data/examples/echo_server.rb +18 -40
  10. data/examples/http_server.rb +42 -43
  11. data/examples/inout.rb +19 -0
  12. data/examples/nc.rb +36 -0
  13. data/examples/server_client.rb +64 -0
  14. data/examples/snooze.rb +44 -0
  15. data/examples/write_dev_null.rb +16 -0
  16. data/ext/um/extconf.rb +24 -23
  17. data/ext/um/um.c +524 -278
  18. data/ext/um/um.h +146 -44
  19. data/ext/um/um_buffer.c +49 -0
  20. data/ext/um/um_class.c +217 -106
  21. data/ext/um/um_const.c +213 -0
  22. data/ext/um/um_ext.c +4 -0
  23. data/ext/um/um_mutex_class.c +47 -0
  24. data/ext/um/um_op.c +86 -114
  25. data/ext/um/um_queue_class.c +58 -0
  26. data/ext/um/um_sync.c +273 -0
  27. data/ext/um/um_utils.c +49 -4
  28. data/lib/uringmachine/dns_resolver.rb +84 -0
  29. data/lib/uringmachine/version.rb +1 -1
  30. data/lib/uringmachine.rb +28 -0
  31. data/supressions/ruby.supp +71 -0
  32. data/test/helper.rb +8 -0
  33. data/test/test_um.rb +685 -46
  34. data/vendor/liburing/.github/workflows/build.yml +29 -1
  35. data/vendor/liburing/.gitignore +6 -0
  36. data/vendor/liburing/CHANGELOG +16 -0
  37. data/vendor/liburing/CONTRIBUTING.md +165 -0
  38. data/vendor/liburing/configure +64 -0
  39. data/vendor/liburing/examples/Makefile +9 -1
  40. data/vendor/liburing/examples/kdigest.c +405 -0
  41. data/vendor/liburing/examples/proxy.c +75 -8
  42. data/vendor/liburing/examples/reg-wait.c +159 -0
  43. data/vendor/liburing/liburing.pc.in +1 -1
  44. data/vendor/liburing/liburing.spec +1 -1
  45. data/vendor/liburing/src/Makefile +16 -2
  46. data/vendor/liburing/src/include/liburing/io_uring.h +77 -0
  47. data/vendor/liburing/src/include/liburing/sanitize.h +39 -0
  48. data/vendor/liburing/src/include/liburing.h +59 -6
  49. data/vendor/liburing/src/int_flags.h +10 -3
  50. data/vendor/liburing/src/liburing-ffi.map +16 -0
  51. data/vendor/liburing/src/liburing.map +10 -0
  52. data/vendor/liburing/src/queue.c +28 -16
  53. data/vendor/liburing/src/register.c +106 -1
  54. data/vendor/liburing/src/sanitize.c +176 -0
  55. data/vendor/liburing/src/setup.c +47 -19
  56. data/vendor/liburing/src/setup.h +6 -0
  57. data/vendor/liburing/test/35fa71a030ca.c +7 -0
  58. data/vendor/liburing/test/500f9fbadef8.c +2 -0
  59. data/vendor/liburing/test/7ad0e4b2f83c.c +0 -25
  60. data/vendor/liburing/test/917257daa0fe.c +7 -0
  61. data/vendor/liburing/test/Makefile +38 -4
  62. data/vendor/liburing/test/a0908ae19763.c +7 -0
  63. data/vendor/liburing/test/a4c0b3decb33.c +7 -0
  64. data/vendor/liburing/test/accept.c +14 -4
  65. data/vendor/liburing/test/b19062a56726.c +7 -0
  66. data/vendor/liburing/test/bind-listen.c +2 -2
  67. data/vendor/liburing/test/buf-ring-nommap.c +10 -3
  68. data/vendor/liburing/test/buf-ring.c +2 -0
  69. data/vendor/liburing/test/cmd-discard.c +427 -0
  70. data/vendor/liburing/test/coredump.c +7 -0
  71. data/vendor/liburing/test/cq-overflow.c +13 -1
  72. data/vendor/liburing/test/d4ae271dfaae.c +11 -3
  73. data/vendor/liburing/test/defer-taskrun.c +2 -2
  74. data/vendor/liburing/test/defer-tw-timeout.c +4 -1
  75. data/vendor/liburing/test/defer.c +2 -2
  76. data/vendor/liburing/test/double-poll-crash.c +1 -1
  77. data/vendor/liburing/test/eeed8b54e0df.c +2 -0
  78. data/vendor/liburing/test/eventfd.c +0 -1
  79. data/vendor/liburing/test/exit-no-cleanup.c +11 -0
  80. data/vendor/liburing/test/fadvise.c +9 -26
  81. data/vendor/liburing/test/fdinfo.c +9 -1
  82. data/vendor/liburing/test/fifo-nonblock-read.c +69 -0
  83. data/vendor/liburing/test/file-exit-unreg.c +48 -0
  84. data/vendor/liburing/test/file-register.c +14 -2
  85. data/vendor/liburing/test/file-update.c +1 -1
  86. data/vendor/liburing/test/file-verify.c +27 -16
  87. data/vendor/liburing/test/files-exit-hang-timeout.c +1 -2
  88. data/vendor/liburing/test/fixed-buf-iter.c +3 -1
  89. data/vendor/liburing/test/fixed-hugepage.c +12 -1
  90. data/vendor/liburing/test/fsnotify.c +1 -0
  91. data/vendor/liburing/test/futex.c +16 -4
  92. data/vendor/liburing/test/helpers.c +47 -0
  93. data/vendor/liburing/test/helpers.h +6 -0
  94. data/vendor/liburing/test/init-mem.c +5 -3
  95. data/vendor/liburing/test/io-cancel.c +0 -24
  96. data/vendor/liburing/test/io_uring_passthrough.c +4 -0
  97. data/vendor/liburing/test/io_uring_register.c +38 -8
  98. data/vendor/liburing/test/iopoll-leak.c +4 -0
  99. data/vendor/liburing/test/iopoll-overflow.c +1 -1
  100. data/vendor/liburing/test/iopoll.c +3 -3
  101. data/vendor/liburing/test/kallsyms.c +203 -0
  102. data/vendor/liburing/test/link-timeout.c +159 -0
  103. data/vendor/liburing/test/linked-defer-close.c +224 -0
  104. data/vendor/liburing/test/madvise.c +12 -25
  105. data/vendor/liburing/test/min-timeout-wait.c +0 -25
  106. data/vendor/liburing/test/min-timeout.c +0 -25
  107. data/vendor/liburing/test/mkdir.c +6 -0
  108. data/vendor/liburing/test/msg-ring.c +8 -2
  109. data/vendor/liburing/test/napi-test.c +16 -3
  110. data/vendor/liburing/test/no-mmap-inval.c +3 -1
  111. data/vendor/liburing/test/nop.c +44 -0
  112. data/vendor/liburing/test/ooo-file-unreg.c +1 -1
  113. data/vendor/liburing/test/open-close.c +40 -0
  114. data/vendor/liburing/test/openat2.c +37 -14
  115. data/vendor/liburing/test/poll-many.c +13 -7
  116. data/vendor/liburing/test/poll-mshot-update.c +17 -10
  117. data/vendor/liburing/test/poll-v-poll.c +6 -3
  118. data/vendor/liburing/test/pollfree.c +148 -0
  119. data/vendor/liburing/test/read-mshot-empty.c +158 -153
  120. data/vendor/liburing/test/read-mshot-stdin.c +121 -0
  121. data/vendor/liburing/test/read-mshot.c +282 -27
  122. data/vendor/liburing/test/read-write.c +78 -13
  123. data/vendor/liburing/test/recv-msgall-stream.c +3 -0
  124. data/vendor/liburing/test/recv-msgall.c +5 -0
  125. data/vendor/liburing/test/recvsend_bundle-inc.c +680 -0
  126. data/vendor/liburing/test/recvsend_bundle.c +94 -31
  127. data/vendor/liburing/test/reg-fd-only.c +15 -5
  128. data/vendor/liburing/test/reg-wait.c +251 -0
  129. data/vendor/liburing/test/regbuf-clone.c +645 -0
  130. data/vendor/liburing/test/regbuf-merge.c +7 -0
  131. data/vendor/liburing/test/register-restrictions.c +86 -85
  132. data/vendor/liburing/test/rename.c +59 -1
  133. data/vendor/liburing/test/resize-rings.c +643 -0
  134. data/vendor/liburing/test/ringbuf-read.c +5 -0
  135. data/vendor/liburing/test/ringbuf-status.c +5 -1
  136. data/vendor/liburing/test/rsrc_tags.c +1 -1
  137. data/vendor/liburing/test/runtests.sh +16 -1
  138. data/vendor/liburing/test/send-zerocopy.c +59 -0
  139. data/vendor/liburing/test/short-read.c +1 -0
  140. data/vendor/liburing/test/socket.c +43 -0
  141. data/vendor/liburing/test/splice.c +3 -1
  142. data/vendor/liburing/test/sq-poll-dup.c +1 -1
  143. data/vendor/liburing/test/sq-poll-share.c +2 -0
  144. data/vendor/liburing/test/sqpoll-disable-exit.c +8 -0
  145. data/vendor/liburing/test/sqpoll-exit-hang.c +1 -25
  146. data/vendor/liburing/test/sqpoll-sleep.c +40 -33
  147. data/vendor/liburing/test/sqwait.c +136 -0
  148. data/vendor/liburing/test/statx.c +89 -0
  149. data/vendor/liburing/test/stdout.c +2 -0
  150. data/vendor/liburing/test/submit-and-wait.c +1 -25
  151. data/vendor/liburing/test/submit-reuse.c +4 -26
  152. data/vendor/liburing/test/symlink.c +12 -1
  153. data/vendor/liburing/test/sync-cancel.c +56 -22
  154. data/vendor/liburing/test/thread-exit.c +5 -0
  155. data/vendor/liburing/test/timeout-new.c +1 -26
  156. data/vendor/liburing/test/timeout.c +25 -34
  157. data/vendor/liburing/test/unlink.c +94 -1
  158. data/vendor/liburing/test/uring_cmd_ublk.c +1252 -0
  159. data/vendor/liburing/test/waitid.c +62 -8
  160. data/vendor/liburing/test/wq-aff.c +35 -0
  161. data/vendor/liburing/test/xfail_prep_link_timeout_out_of_scope.c +46 -0
  162. data/vendor/liburing/test/xfail_register_buffers_out_of_scope.c +51 -0
  163. metadata +37 -6
  164. data/examples/event_loop.rb +0 -69
  165. data/examples/fibers.rb +0 -105
  166. data/examples/http_server_multishot.rb +0 -57
  167. data/examples/http_server_simpler.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7129b3c8605d5734f7152bddb4fa1b6f034fd1de26262eabfbfc2190846967bd
4
- data.tar.gz: 902e6663bac65d56e45d67383e2cd3eb0601534ed2b451f4f76b2812ce6125b1
3
+ metadata.gz: 2067cd0f88d6dbc341410ca6911916f510132ee23ce3f6d5fb5978259eeaef11
4
+ data.tar.gz: f816a9478f714a3a9770740bc14fc03ae2687f2fe7d0e29cbc0b8632994bad1f
5
5
  SHA512:
6
- metadata.gz: 683db63642ddb5d98c9eb137f8121a8cb8cb0fe44456928fca78cf8e322cebc01078efa71379abcceae1a1889140bf5b39f6ac16348db0b77d9c8a5bb9cf5cd0
7
- data.tar.gz: 850dce780030b6102371861805f831299d3ea113f8c02141515afe167b0e49f43a51812a6159a91b6335085b41fcb226713eeb963ae69d208818cb1f3cb303b3
6
+ metadata.gz: 6e0a9ed6e6f131bf6d67a56622786893532a525f4427e134670499997c0461a4df6de3ca1cb9e70851769c1135575986468a83edfd9b5d75c7d592e41bb0ca8c
7
+ data.tar.gz: 2c73ed9a3268ea96b3a167064251d16279c531ee50e5f9c6de9aa2c56f6a70973ef04ce5d6c44f46e39309f7bf8cb3f3ff5e61bf1512ca07cc5f35b91f7303dc
@@ -32,4 +32,5 @@ jobs:
32
32
  - name: Compile C-extension
33
33
  run: bundle exec rake compile
34
34
  - name: Run tests
35
- run: bundle exec rake test
35
+ # run: bundle exec ruby test/test_um.rb --name test_read_each_raising_2
36
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ # 2024-11-14 Version 0.5
2
+
3
+ - Add `#waitpid`
4
+ - Add `UM.pipe`, `UM.kernel_version`
5
+ - Add `#open`
6
+ - Fix `#spin`
7
+ - Fix handling of signal interruption.
8
+ - Reimplement and simplify um_op
9
+ - Add `UM::Queue`, `#push`, `#pop`, `#shift`, `#unshift`
10
+ - Add `UM::Mutex`, `#synchronize`
11
+ - Add `#recv_each`
12
+ - Add `#getsockopt`, `#setsockopt`
13
+ - Simplify and improve op management
14
+
15
+ # 2024-10-06 Version 0.4
16
+
17
+ - Add socket constants
18
+ - Add `#bind`, `#listen`
19
+ - Add `#spin`
20
+ - Fix bugs in multishot read and other ops
21
+ - Add `#recv`, `#send`
22
+ - Add `#socket`, `#connect`
23
+
1
24
  # 2024-10-04 Version 0.3
2
25
 
3
26
  - Fix race condition affecting `#timeout` and `#sleep`.
data/README.md CHANGED
@@ -9,3 +9,131 @@
9
9
  <a href="https://github.com/digital-fabric/uringmachine/blob/master/LICENSE">
10
10
  <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
11
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).
16
+
17
+ ## Features
18
+
19
+ - Automatic fiber switching when performing blocking operations.
20
+ - Automatic cancellation using of ongoing operations with Ruby exceptions.
21
+ - 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.
24
+
25
+ ## Design
26
+
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
97
+
98
+ ```ruby
99
+ require 'uringmachine'
100
+
101
+ machine = UringMachine.new
102
+ stdout_fd = STDOUT.fileno
103
+ stdin_fd = STDIN.fileno
104
+ machine.write(stdout_fd, "Hello, world!\n")
105
+
106
+ loop do
107
+ machine.write(stdout_fd, "Say something: ")
108
+ 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
114
+ end
115
+ end
116
+ ```
117
+
118
+ ## Concurrent Execution
119
+
120
+ Concurrent execution is done by calling `#spin`, which creates a fiber:
121
+
122
+ ```ruby
123
+ machine = UringMachine.new
124
+
125
+ rfd, wfd = machine.pipe
126
+
127
+ f1 = machine.spin do
128
+ machine.write(wfd, 'hello')
129
+ machine.write(wfd, 'world')
130
+ machine.close(wfd)
131
+ end
132
+
133
+ bgid = machine.setup_buffer_ring(4096, 1024)
134
+ f2 = machine.spin do
135
+ machine.read_each(rfd, bgid) do |str|
136
+ puts str
137
+ end
138
+ end
139
+ ```
data/TODO.md CHANGED
@@ -0,0 +1,14 @@
1
+ - splice / - tee
2
+ - sendto
3
+ - recvfrom
4
+ - poll_add / poll_remove / poll_multishot / poll_update
5
+ - fsync
6
+ - mkdir / mkdirat
7
+ - statx
8
+ - link / linkat / unlink / unlinkat / symlink
9
+ - rename / renameat
10
+ - fadvise
11
+ - madvise
12
+ - getxattr / setxattr
13
+ - shutdown
14
+ - send_bundle / recv_bundle (kernel >= 6.10)
@@ -0,0 +1,89 @@
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-ips'
9
+ end
10
+
11
+ require 'benchmark/ips'
12
+ require 'uringmachine'
13
+
14
+ ITERATIONS = 1000
15
+
16
+ $machine = UringMachine.new
17
+
18
+ def run_snooze
19
+ count = 0
20
+ main = Fiber.current
21
+
22
+ f1 = Fiber.new do
23
+ loop do
24
+ count += 1
25
+ if count == ITERATIONS
26
+ $machine.schedule(main, nil)
27
+ break
28
+ else
29
+ $machine.snooze
30
+ end
31
+ end
32
+ end
33
+
34
+ f2 = Fiber.new do
35
+ loop do
36
+ count += 1
37
+ if count == ITERATIONS
38
+ $machine.schedule(main, nil)
39
+ break
40
+ else
41
+ $machine.snooze
42
+ end
43
+ end
44
+ end
45
+
46
+ $machine.schedule(f1, nil)
47
+ $machine.schedule(f2, nil)
48
+ $machine.yield
49
+ end
50
+
51
+ def run_raw_transfer
52
+ count = 0
53
+ main = Fiber.current
54
+ f2 = nil
55
+ f1 = Fiber.new do
56
+ loop do
57
+ count += 1
58
+ if count == ITERATIONS
59
+ main.transfer(nil)
60
+ break
61
+ else
62
+ f2.transfer(nil)
63
+ end
64
+ end
65
+ end
66
+
67
+ f2 = Fiber.new do
68
+ loop do
69
+ count += 1
70
+ if count == ITERATIONS
71
+ main.transfer(nil)
72
+ break
73
+ else
74
+ f1.transfer(nil)
75
+ end
76
+ end
77
+ end
78
+
79
+ f1.transfer(nil)
80
+ end
81
+
82
+ bm = Benchmark.ips do |x|
83
+ x.config(:time => 5, :warmup => 2)
84
+
85
+ x.report("snooze") { run_snooze }
86
+ x.report("raw transfer") { run_raw_transfer }
87
+
88
+ x.compare!
89
+ end
@@ -0,0 +1,56 @@
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
+ end
10
+
11
+ require 'benchmark'
12
+ require 'uringmachine'
13
+
14
+ ITERATIONS = 100000
15
+ BUF = ('*' * 8192).freeze
16
+ FN = '/tmp/bm_write'
17
+
18
+ def run_io_write(num_threads)
19
+ FileUtils.rm(FN) rescue nil
20
+ fio = File.open(FN, 'w')
21
+
22
+ threads = num_threads.times.map do |i|
23
+ Thread.new do
24
+ ITERATIONS.times { fio.write(BUF) }
25
+ end
26
+ end
27
+ threads.each(&:join)
28
+ ensure
29
+ fio.close
30
+ end
31
+
32
+ def run_um_write(num_fibers)
33
+ FileUtils.rm(FN) rescue nil
34
+ fio = File.open(FN, 'w')
35
+ fd = fio.fileno
36
+
37
+ machine = UringMachine.new
38
+ done = UringMachine::Queue.new
39
+ num_fibers.times do
40
+ machine.spin do
41
+ ITERATIONS.times { machine.write(fd, BUF) }
42
+ machine.push(done, true)
43
+ end
44
+ end
45
+ num_fibers.times { machine.pop(done) }
46
+ ensure
47
+ fio.close
48
+ end
49
+
50
+ Benchmark.bm do |x|
51
+ [1, 2, 4, 8].each do |c|
52
+ x.report("IO (#{c} threads)") { run_io_write(c) }
53
+ x.report("UM (#{c} fibers) ") { run_um_write(c) }
54
+ puts
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+ require 'resolv'
5
+
6
+ machine = UM.new
7
+
8
+ addrs = machine.resolve('status.realiteq.net')
9
+
10
+ puts '*' * 40
11
+ puts addrs.join("\n")
12
+ puts
@@ -1,52 +1,30 @@
1
- require_relative '../lib/iou'
1
+ require_relative '../lib/uringmachine'
2
2
  require 'socket'
3
3
 
4
4
  socket = TCPServer.open('127.0.0.1', 1234)
5
5
  puts 'Listening on port 1234...'
6
6
 
7
- @ring = IOU::Ring.new
7
+ $machine = UringMachine.new
8
+ $bgid = $machine.setup_buffer_ring(4096, 1024)
8
9
 
9
- @ring.prep_accept(fd: socket.fileno, multishot: true) do |c|
10
- setup_connection(c[:result]) if c[:result] > 0
11
- end
12
-
13
- def setup_connection(fd)
14
- puts "Connection accepted fd #{fd}"
15
-
16
- buffer = +''
17
- echo_prep_read(fd, buffer)
18
- end
19
-
20
- def echo_prep_read(fd, buffer)
21
- @ring.prep_read(fd: fd, buffer: buffer, len: 4096, buffer_offset: -1) do |c|
22
- if c[:result] > 0
23
- echo_lines(fd, buffer)
24
- echo_prep_read(fd, buffer)
25
- elsif c[:result] == 0
26
- puts "Connection closed by client on fd #{fd}"
27
- else
28
- puts "Got error #{c[:result]} on fd #{fd}, closing connection..."
29
- @ring.prep_close(fd: fd) do |c|
30
- puts "Connection closed on fd #{fd}, result #{c[:result]}"
31
- end
32
- end
10
+ def handle_connection(fd)
11
+ $machine.read_each(fd, $bgid) do |buf|
12
+ $machine.write(fd, buf)
33
13
  end
14
+ puts "Connection closed by client fd #{fd}"
15
+ rescue Exception => e
16
+ puts "Got error #{e.inspect}, closing connection"
17
+ $machine.close(fd) rescue nil
34
18
  end
35
19
 
36
- def echo_lines(fd, buffer)
37
- sep = $/
38
- sep_size = sep.bytesize
39
-
40
- while true
41
- idx = buffer.index(sep)
42
- if idx
43
- line = buffer.slice!(0, idx + sep_size)
44
- @ring.prep_write(fd: fd, buffer: line)
45
- else
46
- break
47
- end
20
+ $machine.spin do
21
+ loop do
22
+ $machine.sleep 5
23
+ puts "pending: #{$machine.pending_count}"
48
24
  end
49
25
  end
50
26
 
51
- trap('SIGINT') { exit! }
52
- @ring.process_completions_loop
27
+ $machine.accept_each(socket.fileno) do |fd|
28
+ puts "Connection accepted fd #{fd}"
29
+ $machine.spin(fd) { handle_connection(_1) }
30
+ end
@@ -1,56 +1,55 @@
1
- require_relative '../lib/iou'
2
- require 'socket'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
3
4
  require 'http/parser'
4
5
 
5
- def log(msg)
6
- # return
7
- STDERR.puts msg
8
- end
6
+ @machine = UM.new
7
+ @bgid = @machine.setup_buffer_ring(4096, 1024)
9
8
 
10
- socket = TCPServer.open('127.0.0.1', 1234)
11
- log 'Listening on port 1234...'
9
+ def http_handle_connection(fd)
10
+ # puts "Accepting connection on fd #{fd}"
11
+ parser = Http::Parser.new
12
+ done = nil
13
+ parser.on_message_complete = -> do
14
+ http_send_response(fd, "Hello, world!\n")
15
+ done = true
16
+ end
12
17
 
13
- @ring = IOU::Ring.new
14
- @bg_id = @ring.setup_buffer_ring(count: 1024, size: 4096)
18
+ @machine.read_each(fd, @bgid) do
19
+ parser << _1
20
+ break if done
21
+ end
15
22
 
16
- @ring.prep_accept(fd: socket.fileno, multishot: true) do |c|
17
- setup_connection(c[:result]) if c[:result] > 0
23
+ # puts "Connection closed on fd #{fd}"
24
+ rescue => e
25
+ # puts "Error while handling connection on fd #{fd}: #{e.inspect}"
26
+ ensure
27
+ @machine.close(fd) rescue nil
18
28
  end
19
29
 
20
- def setup_connection(fd)
21
- log "Connection accepted fd #{fd}"
22
-
23
- parser = Http::Parser.new
24
- parser.on_message_complete = -> {
25
- http_send_response(fd, "Hello, world!\n") do
26
- @ring.prep_close(fd: fd)
27
- end
28
- }
29
-
30
- http_prep_read(fd, parser)
30
+ def http_send_response(fd, body)
31
+ # msg = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: keep-alive\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
32
+ msg = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
33
+ @machine.write(fd, msg)
31
34
  end
32
35
 
33
- def http_prep_read(fd, parser)
34
- buffer = +''
35
- @ring.prep_read(fd: fd, buffer: buffer, len: 4096) do |c|
36
- if c[:result] > 0
37
- http_prep_read(fd, parser)
38
- parser << buffer
39
- elsif c[:result] == 0
40
- log "Connection closed by client on fd #{fd}"
41
- else
42
- log "Got error #{c[:result]} on fd #{fd}, closing connection..."
43
- @ring.prep_close(fd: fd) do |c|
44
- log "Connection closed on fd #{fd}, result #{c[:result]}"
45
- end
46
- end
36
+ server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
37
+ @machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
38
+ @machine.bind(server_fd, '127.0.0.1', 1234)
39
+ @machine.listen(server_fd, UM::SOMAXCONN)
40
+ puts 'Listening on port 1234'
41
+
42
+ @machine.spin do
43
+ @machine.accept_each(server_fd) do |fd|
44
+ @machine.spin(fd) { http_handle_connection _1 }
47
45
  end
48
46
  end
49
47
 
50
- def http_send_response(fd, body)
51
- msg = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: keep-alive\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
52
- @ring.prep_write(fd: fd, buffer: msg)
53
- end
48
+ main = Fiber.current
49
+ trap('SIGINT') { @machine.schedule(main, nil) }
50
+ trap('SIGTERM') { @machine.schedule(main, nil) }
54
51
 
55
- trap('SIGINT') { exit! }
56
- @ring.process_completions_loop
52
+ @machine.yield
53
+ puts "Closing server FD"
54
+ @machine.close(server_fd) rescue nil
55
+ puts "done!"
data/examples/inout.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+
5
+ machine = UringMachine.new
6
+ stdout_fd = STDOUT.fileno
7
+ stdin_fd = STDIN.fileno
8
+ machine.write(stdout_fd, "Hello, world!\n")
9
+
10
+ loop do
11
+ machine.write(stdout_fd, "Say something: ")
12
+ buf = +''
13
+ res = machine.read(stdin_fd, buf, 8192)
14
+ if res > 0
15
+ machine.write(stdout_fd, "You said: #{buf}")
16
+ else
17
+ break
18
+ end
19
+ end
data/examples/nc.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+ require 'socket'
5
+
6
+ HOST = ARGV[0]
7
+ PORT = ARGV[1].to_i
8
+
9
+ machine = UringMachine.new
10
+
11
+ conn_fd = machine.socket(Socket::AF_INET, Socket::SOCK_STREAM, 0, 0);
12
+ machine.connect(conn_fd, HOST, PORT)
13
+
14
+ stdin_fd = STDIN.fileno
15
+ stdout_fd = STDOUT.fileno
16
+
17
+ f_writer = Fiber.new do
18
+ bgidw = machine.setup_buffer_ring(4096, 1024)
19
+ machine.read_each(stdin_fd, bgidw) do |buf|
20
+ machine.write(conn_fd, buf)
21
+ end
22
+ end
23
+ machine.schedule(f_writer, nil)
24
+
25
+ f_reader = Fiber.new do
26
+ bgidr = machine.setup_buffer_ring(4096, 1024)
27
+ machine.read_each(conn_fd, bgidr) do |buf|
28
+ machine.write(stdout_fd, buf)
29
+ end
30
+ end
31
+ machine.schedule(f_reader, nil)
32
+
33
+ trap('SIGINT') { exit! }
34
+ loop do
35
+ machine.sleep(60)
36
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+
5
+ PORT = 1234
6
+
7
+ @machine = UM.new
8
+ @bgid = @machine.setup_buffer_ring(4096, 1024)
9
+
10
+ @counter = 0
11
+
12
+ def handle_connection(fd)
13
+ buf = +''
14
+ loop do
15
+ res = @machine.recv(fd, buf, 8192, 0)
16
+ break if res == 0
17
+
18
+ @machine.write(fd, buf)
19
+ @counter += 2
20
+ end
21
+ ensure
22
+ @machine.close(fd) rescue nil
23
+ end
24
+
25
+ def run_client
26
+ fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
27
+ @machine.connect(fd, '127.0.0.1', PORT)
28
+ msg = 'foo' * 30
29
+ buf = +''
30
+ loop do
31
+ @machine.send(fd, msg, msg.bytesize, 0)
32
+ res = @machine.recv(fd, buf, 8192, 0)
33
+ @counter += 2
34
+
35
+ break if res == 0
36
+ raise "Got #{res} bytes instead of #{msg.bytesize}" if res != msg.bytesize
37
+ end
38
+ end
39
+
40
+ trap('SIGINT') { exit }
41
+
42
+ server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
43
+ @machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
44
+ @machine.bind(server_fd, '127.0.0.1', PORT)
45
+ @machine.listen(server_fd, UM::SOMAXCONN)
46
+ puts "Listening on port #{PORT}"
47
+
48
+ at_exit { @machine.close(server_fd) rescue nil }
49
+
50
+ 20.times do
51
+ @machine.spin { run_client }
52
+ end
53
+
54
+ @machine.spin do
55
+ @machine.accept_each(server_fd) do |fd|
56
+ @machine.spin(fd) { handle_connection _1 }
57
+ end
58
+ end
59
+
60
+ t0 = Time.now
61
+ @machine.sleep 3
62
+ t1 = Time.now
63
+ elapsed = t1 - t0
64
+ puts "Did #{@counter} ops in #{elapsed} seconds (#{(@counter / elapsed)} ops/s)"
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/uringmachine'
4
+
5
+ @machine = UM.new
6
+
7
+ @counter = 0
8
+ @fiber_count = 0
9
+
10
+ def start_fiber
11
+ @fiber_count += 1
12
+ @machine.spin do
13
+ max_count = @counter + rand(1000)
14
+ puts "Start #{Fiber.current} #{max_count - @counter}"
15
+ loop do
16
+ @machine.sleep 0.001
17
+ @counter += 1
18
+ break if @counter >= max_count
19
+ end
20
+ puts "Stop #{Fiber.current}"
21
+ ensure
22
+ @fiber_count -= 1
23
+ end
24
+ end
25
+
26
+ t0 = Time.now
27
+ MAX_FIBERS = 20
28
+ MAX_TIME = 10
29
+ loop do
30
+ @machine.sleep 0.1
31
+ puts "pending: #{@machine.pending_count}"
32
+ break if (Time.now - t0) > MAX_TIME
33
+ start_fiber while @fiber_count < MAX_FIBERS
34
+ end
35
+ t1 = Time.now
36
+ elapsed = t1 - t0
37
+ rate = @counter / elapsed
38
+ puts "Did #{@counter} ops in #{elapsed} seconds (#{rate} ops/s)"
39
+
40
+ puts "Waiting for fibers... (#{@fiber_count})"
41
+ while @fiber_count > 0
42
+ @machine.sleep 0.1
43
+ end
44
+ puts "Done"