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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -1
- data/CHANGELOG.md +23 -0
- data/README.md +128 -0
- data/TODO.md +14 -0
- data/examples/bm_snooze.rb +89 -0
- data/examples/bm_write.rb +56 -0
- data/examples/dns_client.rb +12 -0
- data/examples/echo_server.rb +18 -40
- data/examples/http_server.rb +42 -43
- data/examples/inout.rb +19 -0
- data/examples/nc.rb +36 -0
- data/examples/server_client.rb +64 -0
- data/examples/snooze.rb +44 -0
- data/examples/write_dev_null.rb +16 -0
- data/ext/um/extconf.rb +24 -23
- data/ext/um/um.c +524 -278
- data/ext/um/um.h +146 -44
- data/ext/um/um_buffer.c +49 -0
- data/ext/um/um_class.c +217 -106
- data/ext/um/um_const.c +213 -0
- data/ext/um/um_ext.c +4 -0
- data/ext/um/um_mutex_class.c +47 -0
- data/ext/um/um_op.c +86 -114
- data/ext/um/um_queue_class.c +58 -0
- data/ext/um/um_sync.c +273 -0
- data/ext/um/um_utils.c +49 -4
- data/lib/uringmachine/dns_resolver.rb +84 -0
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +28 -0
- data/supressions/ruby.supp +71 -0
- data/test/helper.rb +8 -0
- data/test/test_um.rb +685 -46
- data/vendor/liburing/.github/workflows/build.yml +29 -1
- data/vendor/liburing/.gitignore +6 -0
- data/vendor/liburing/CHANGELOG +16 -0
- data/vendor/liburing/CONTRIBUTING.md +165 -0
- data/vendor/liburing/configure +64 -0
- data/vendor/liburing/examples/Makefile +9 -1
- data/vendor/liburing/examples/kdigest.c +405 -0
- data/vendor/liburing/examples/proxy.c +75 -8
- data/vendor/liburing/examples/reg-wait.c +159 -0
- data/vendor/liburing/liburing.pc.in +1 -1
- data/vendor/liburing/liburing.spec +1 -1
- data/vendor/liburing/src/Makefile +16 -2
- data/vendor/liburing/src/include/liburing/io_uring.h +77 -0
- data/vendor/liburing/src/include/liburing/sanitize.h +39 -0
- data/vendor/liburing/src/include/liburing.h +59 -6
- data/vendor/liburing/src/int_flags.h +10 -3
- data/vendor/liburing/src/liburing-ffi.map +16 -0
- data/vendor/liburing/src/liburing.map +10 -0
- data/vendor/liburing/src/queue.c +28 -16
- data/vendor/liburing/src/register.c +106 -1
- data/vendor/liburing/src/sanitize.c +176 -0
- data/vendor/liburing/src/setup.c +47 -19
- data/vendor/liburing/src/setup.h +6 -0
- data/vendor/liburing/test/35fa71a030ca.c +7 -0
- data/vendor/liburing/test/500f9fbadef8.c +2 -0
- data/vendor/liburing/test/7ad0e4b2f83c.c +0 -25
- data/vendor/liburing/test/917257daa0fe.c +7 -0
- data/vendor/liburing/test/Makefile +38 -4
- data/vendor/liburing/test/a0908ae19763.c +7 -0
- data/vendor/liburing/test/a4c0b3decb33.c +7 -0
- data/vendor/liburing/test/accept.c +14 -4
- data/vendor/liburing/test/b19062a56726.c +7 -0
- data/vendor/liburing/test/bind-listen.c +2 -2
- data/vendor/liburing/test/buf-ring-nommap.c +10 -3
- data/vendor/liburing/test/buf-ring.c +2 -0
- data/vendor/liburing/test/cmd-discard.c +427 -0
- data/vendor/liburing/test/coredump.c +7 -0
- data/vendor/liburing/test/cq-overflow.c +13 -1
- data/vendor/liburing/test/d4ae271dfaae.c +11 -3
- data/vendor/liburing/test/defer-taskrun.c +2 -2
- data/vendor/liburing/test/defer-tw-timeout.c +4 -1
- data/vendor/liburing/test/defer.c +2 -2
- data/vendor/liburing/test/double-poll-crash.c +1 -1
- data/vendor/liburing/test/eeed8b54e0df.c +2 -0
- data/vendor/liburing/test/eventfd.c +0 -1
- data/vendor/liburing/test/exit-no-cleanup.c +11 -0
- data/vendor/liburing/test/fadvise.c +9 -26
- data/vendor/liburing/test/fdinfo.c +9 -1
- data/vendor/liburing/test/fifo-nonblock-read.c +69 -0
- data/vendor/liburing/test/file-exit-unreg.c +48 -0
- data/vendor/liburing/test/file-register.c +14 -2
- data/vendor/liburing/test/file-update.c +1 -1
- data/vendor/liburing/test/file-verify.c +27 -16
- data/vendor/liburing/test/files-exit-hang-timeout.c +1 -2
- data/vendor/liburing/test/fixed-buf-iter.c +3 -1
- data/vendor/liburing/test/fixed-hugepage.c +12 -1
- data/vendor/liburing/test/fsnotify.c +1 -0
- data/vendor/liburing/test/futex.c +16 -4
- data/vendor/liburing/test/helpers.c +47 -0
- data/vendor/liburing/test/helpers.h +6 -0
- data/vendor/liburing/test/init-mem.c +5 -3
- data/vendor/liburing/test/io-cancel.c +0 -24
- data/vendor/liburing/test/io_uring_passthrough.c +4 -0
- data/vendor/liburing/test/io_uring_register.c +38 -8
- data/vendor/liburing/test/iopoll-leak.c +4 -0
- data/vendor/liburing/test/iopoll-overflow.c +1 -1
- data/vendor/liburing/test/iopoll.c +3 -3
- data/vendor/liburing/test/kallsyms.c +203 -0
- data/vendor/liburing/test/link-timeout.c +159 -0
- data/vendor/liburing/test/linked-defer-close.c +224 -0
- data/vendor/liburing/test/madvise.c +12 -25
- data/vendor/liburing/test/min-timeout-wait.c +0 -25
- data/vendor/liburing/test/min-timeout.c +0 -25
- data/vendor/liburing/test/mkdir.c +6 -0
- data/vendor/liburing/test/msg-ring.c +8 -2
- data/vendor/liburing/test/napi-test.c +16 -3
- data/vendor/liburing/test/no-mmap-inval.c +3 -1
- data/vendor/liburing/test/nop.c +44 -0
- data/vendor/liburing/test/ooo-file-unreg.c +1 -1
- data/vendor/liburing/test/open-close.c +40 -0
- data/vendor/liburing/test/openat2.c +37 -14
- data/vendor/liburing/test/poll-many.c +13 -7
- data/vendor/liburing/test/poll-mshot-update.c +17 -10
- data/vendor/liburing/test/poll-v-poll.c +6 -3
- data/vendor/liburing/test/pollfree.c +148 -0
- data/vendor/liburing/test/read-mshot-empty.c +158 -153
- data/vendor/liburing/test/read-mshot-stdin.c +121 -0
- data/vendor/liburing/test/read-mshot.c +282 -27
- data/vendor/liburing/test/read-write.c +78 -13
- data/vendor/liburing/test/recv-msgall-stream.c +3 -0
- data/vendor/liburing/test/recv-msgall.c +5 -0
- data/vendor/liburing/test/recvsend_bundle-inc.c +680 -0
- data/vendor/liburing/test/recvsend_bundle.c +94 -31
- data/vendor/liburing/test/reg-fd-only.c +15 -5
- data/vendor/liburing/test/reg-wait.c +251 -0
- data/vendor/liburing/test/regbuf-clone.c +645 -0
- data/vendor/liburing/test/regbuf-merge.c +7 -0
- data/vendor/liburing/test/register-restrictions.c +86 -85
- data/vendor/liburing/test/rename.c +59 -1
- data/vendor/liburing/test/resize-rings.c +643 -0
- data/vendor/liburing/test/ringbuf-read.c +5 -0
- data/vendor/liburing/test/ringbuf-status.c +5 -1
- data/vendor/liburing/test/rsrc_tags.c +1 -1
- data/vendor/liburing/test/runtests.sh +16 -1
- data/vendor/liburing/test/send-zerocopy.c +59 -0
- data/vendor/liburing/test/short-read.c +1 -0
- data/vendor/liburing/test/socket.c +43 -0
- data/vendor/liburing/test/splice.c +3 -1
- data/vendor/liburing/test/sq-poll-dup.c +1 -1
- data/vendor/liburing/test/sq-poll-share.c +2 -0
- data/vendor/liburing/test/sqpoll-disable-exit.c +8 -0
- data/vendor/liburing/test/sqpoll-exit-hang.c +1 -25
- data/vendor/liburing/test/sqpoll-sleep.c +40 -33
- data/vendor/liburing/test/sqwait.c +136 -0
- data/vendor/liburing/test/statx.c +89 -0
- data/vendor/liburing/test/stdout.c +2 -0
- data/vendor/liburing/test/submit-and-wait.c +1 -25
- data/vendor/liburing/test/submit-reuse.c +4 -26
- data/vendor/liburing/test/symlink.c +12 -1
- data/vendor/liburing/test/sync-cancel.c +56 -22
- data/vendor/liburing/test/thread-exit.c +5 -0
- data/vendor/liburing/test/timeout-new.c +1 -26
- data/vendor/liburing/test/timeout.c +25 -34
- data/vendor/liburing/test/unlink.c +94 -1
- data/vendor/liburing/test/uring_cmd_ublk.c +1252 -0
- data/vendor/liburing/test/waitid.c +62 -8
- data/vendor/liburing/test/wq-aff.c +35 -0
- data/vendor/liburing/test/xfail_prep_link_timeout_out_of_scope.c +46 -0
- data/vendor/liburing/test/xfail_register_buffers_out_of_scope.c +51 -0
- metadata +37 -6
- data/examples/event_loop.rb +0 -69
- data/examples/fibers.rb +0 -105
- data/examples/http_server_multishot.rb +0 -57
- data/examples/http_server_simpler.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2067cd0f88d6dbc341410ca6911916f510132ee23ce3f6d5fb5978259eeaef11
|
4
|
+
data.tar.gz: f816a9478f714a3a9770740bc14fc03ae2687f2fe7d0e29cbc0b8632994bad1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e0a9ed6e6f131bf6d67a56622786893532a525f4427e134670499997c0461a4df6de3ca1cb9e70851769c1135575986468a83edfd9b5d75c7d592e41bb0ca8c
|
7
|
+
data.tar.gz: 2c73ed9a3268ea96b3a167064251d16279c531ee50e5f9c6de9aa2c56f6a70973ef04ce5d6c44f46e39309f7bf8cb3f3ff5e61bf1512ca07cc5f35b91f7303dc
|
data/.github/workflows/test.yml
CHANGED
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
|
data/examples/echo_server.rb
CHANGED
@@ -1,52 +1,30 @@
|
|
1
|
-
require_relative '../lib/
|
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
|
-
|
7
|
+
$machine = UringMachine.new
|
8
|
+
$bgid = $machine.setup_buffer_ring(4096, 1024)
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
52
|
-
|
27
|
+
$machine.accept_each(socket.fileno) do |fd|
|
28
|
+
puts "Connection accepted fd #{fd}"
|
29
|
+
$machine.spin(fd) { handle_connection(_1) }
|
30
|
+
end
|
data/examples/http_server.rb
CHANGED
@@ -1,56 +1,55 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/uringmachine'
|
3
4
|
require 'http/parser'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
STDERR.puts msg
|
8
|
-
end
|
6
|
+
@machine = UM.new
|
7
|
+
@bgid = @machine.setup_buffer_ring(4096, 1024)
|
9
8
|
|
10
|
-
|
11
|
-
|
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
|
-
@
|
14
|
-
|
18
|
+
@machine.read_each(fd, @bgid) do
|
19
|
+
parser << _1
|
20
|
+
break if done
|
21
|
+
end
|
15
22
|
|
16
|
-
|
17
|
-
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
48
|
+
main = Fiber.current
|
49
|
+
trap('SIGINT') { @machine.schedule(main, nil) }
|
50
|
+
trap('SIGTERM') { @machine.schedule(main, nil) }
|
54
51
|
|
55
|
-
|
56
|
-
|
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)"
|
data/examples/snooze.rb
ADDED
@@ -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"
|