uringmachine 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +85 -0
- data/TODO.md +5 -0
- data/examples/echo_server.rb +18 -40
- data/examples/inout.rb +19 -0
- data/examples/nc.rb +36 -0
- data/ext/um/extconf.rb +6 -15
- data/ext/um/um.c +245 -53
- data/ext/um/um.h +21 -9
- data/ext/um/um_class.c +74 -87
- data/ext/um/um_const.c +184 -0
- data/ext/um/um_op.c +10 -13
- data/ext/um/um_utils.c +48 -3
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +12 -0
- data/test/helper.rb +8 -0
- data/test/test_um.rb +227 -7
- data/vendor/liburing/.github/workflows/build.yml +29 -1
- data/vendor/liburing/.gitignore +1 -0
- data/vendor/liburing/CHANGELOG +15 -0
- data/vendor/liburing/CONTRIBUTING.md +165 -0
- data/vendor/liburing/configure +32 -0
- data/vendor/liburing/examples/Makefile +8 -1
- data/vendor/liburing/examples/kdigest.c +405 -0
- data/vendor/liburing/examples/proxy.c +75 -8
- data/vendor/liburing/liburing.pc.in +1 -1
- data/vendor/liburing/src/Makefile +16 -2
- data/vendor/liburing/src/include/liburing/io_uring.h +31 -0
- data/vendor/liburing/src/include/liburing/sanitize.h +39 -0
- data/vendor/liburing/src/include/liburing.h +31 -4
- data/vendor/liburing/src/liburing-ffi.map +5 -0
- data/vendor/liburing/src/liburing.map +1 -0
- data/vendor/liburing/src/queue.c +3 -0
- data/vendor/liburing/src/register.c +36 -0
- data/vendor/liburing/src/sanitize.c +176 -0
- data/vendor/liburing/src/setup.c +1 -1
- 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 +31 -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/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/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 +2 -0
- data/vendor/liburing/test/io_uring_register.c +25 -6
- 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 +15 -2
- data/vendor/liburing/test/no-mmap-inval.c +2 -0
- 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 +156 -153
- data/vendor/liburing/test/read-mshot.c +276 -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 +92 -29
- data/vendor/liburing/test/reg-fd-only.c +14 -4
- data/vendor/liburing/test/regbuf-clone.c +187 -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/ringbuf-read.c +5 -0
- data/vendor/liburing/test/ringbuf-status.c +5 -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 +1 -25
- 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 +48 -21
- data/vendor/liburing/test/thread-exit.c +5 -0
- data/vendor/liburing/test/timeout-new.c +1 -26
- data/vendor/liburing/test/timeout.c +12 -26
- 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 +17 -4
- data/examples/event_loop.rb +0 -69
- data/examples/fibers.rb +0 -105
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a9efd4af7de9fdb8db6888b9f6347e9dbc63aae9e26fa0f81d43984d265bd90
|
4
|
+
data.tar.gz: 5289699ce1e3e173d3766e0211fc77b57f3c123e2a6e041e9e751a3bf5ce07b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cf02bbaae5a9e19b9ea994d1c2bf4b327a96121b083ca6756eca70629738f6c24aca10096bb49a0400678542232062969889ed3f0773591f5dc71f718707fef
|
7
|
+
data.tar.gz: 67b368097ddecd6c9ca9ce360cc918d8e7de91ec83a00d617765c2026fbe08f16f5009b78323e9f314cd1df63a1baaa39e8887b4a90fff0cb8010d810276df7d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 2024-10-06 Version 0.4
|
2
|
+
|
3
|
+
- Add socket constants
|
4
|
+
- Add `#bind`, `#listen`
|
5
|
+
- Add `#spin`
|
6
|
+
- Fix bugs in multishot read and other ops
|
7
|
+
- Add `#recv`, `#send`
|
8
|
+
- Add `#socket`, `#connect`
|
9
|
+
|
1
10
|
# 2024-10-04 Version 0.3
|
2
11
|
|
3
12
|
- Fix race condition affecting `#timeout` and `#sleep`.
|
data/README.md
CHANGED
@@ -9,3 +9,88 @@
|
|
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
|
+
Some important learnings from those two projects, in no particular order:
|
36
|
+
|
37
|
+
- Monkey-patching is not a good solution, long term. You need to deal with
|
38
|
+
changing APIs (Ruby is evolving quite rapidly these days!), and anyways you're
|
39
|
+
always going to get stuck with some standard Ruby API that's implemented as a
|
40
|
+
C extension and just won't play nice with whatever you're trying to do.
|
41
|
+
- The design of the Polyphony io_uring backend was an evolution of something
|
42
|
+
that was originally based on libev as an event loop. In hindsight, adapting
|
43
|
+
the design for how io_uring worked led to code that was too complex and even
|
44
|
+
somewhat brittle.
|
45
|
+
- IOU showed me that even if we embrace callbacks, the developer experience is
|
46
|
+
substantially inferior to what you can do with a sequential coding style. Even
|
47
|
+
just in terms of line count - with callbacks you end up with roughly double
|
48
|
+
the number of lines of code.
|
49
|
+
- Implementing fiber switching on top of IOU was disappointing in terms of
|
50
|
+
performance. In order for a fiber-based solution to be performed it had to be
|
51
|
+
baked in - hence UringMachine.
|
52
|
+
- Working with fibers has the very important benefit that you can keep stuff on
|
53
|
+
the stack, instead of passing around all kinds of references to the heap. In
|
54
|
+
addition, you mostly don't need to worry about marking Ruby objects used in
|
55
|
+
operations, since normally they'll already be on the stack as method call
|
56
|
+
parameters.
|
57
|
+
- Polyphony was designed as an all-in-one solution that did everything: turning
|
58
|
+
stock APIs into fiber-aware ones, providing a solid structured-concurrency
|
59
|
+
implementation for controlling fiber life times, extensions providing
|
60
|
+
additional features such as compressing streaming data between two fds, other
|
61
|
+
APIs based on splicing etc. Perhaps a more cautious approach would be better.
|
62
|
+
- Pending operation lifetime management in Polyphony was based a complex
|
63
|
+
reference counting scheme that proved problematic, especially for multishot
|
64
|
+
operations.
|
65
|
+
|
66
|
+
So, based on those two projects, I wanted to design a Ruby API for io_uring
|
67
|
+
based on the following principles:
|
68
|
+
|
69
|
+
- Automatic fiber switching.
|
70
|
+
- No monkey-patching. Instead, provide a simple custom API, as a replacement for
|
71
|
+
the stock Ruby `IO` and `Socket` classes.
|
72
|
+
- Simpler management of pending operation lifetime.
|
73
|
+
- Do not insist on structured concurrency, just provide the APIs necessary to
|
74
|
+
create actors and to supervise the execution of fibers.
|
75
|
+
|
76
|
+
## Example
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'uringmachine'
|
80
|
+
|
81
|
+
machine = UringMachine.new
|
82
|
+
stdout_fd = STDOUT.fileno
|
83
|
+
stdin_fd = STDIN.fileno
|
84
|
+
machine.write(stdout_fd, "Hello, world!\n")
|
85
|
+
|
86
|
+
loop do
|
87
|
+
machine.write(stdout_fd, "Say something: ")
|
88
|
+
buf = +''
|
89
|
+
res = machine.read(stdin_fd, buf, 8192)
|
90
|
+
if res > 0
|
91
|
+
machine.write(stdout_fd, "You said: #{buf}")
|
92
|
+
else
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
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/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
|
data/ext/um/extconf.rb
CHANGED
@@ -21,14 +21,11 @@ def get_config
|
|
21
21
|
raise "UringMachine requires kernel version 6.4 or newer!" if combined_version < 604
|
22
22
|
|
23
23
|
config[:kernel_version] = combined_version
|
24
|
-
config[:pidfd_open] = combined_version > 503
|
25
|
-
config[:multishot_accept] = combined_version >= 519
|
26
|
-
config[:multishot_recv] = combined_version >= 600
|
27
|
-
config[:multishot_recvmsg] = combined_version >= 600
|
28
|
-
config[:multishot_timeout] = combined_version >= 604
|
29
24
|
config[:submit_all_flag] = combined_version >= 518
|
30
25
|
config[:coop_taskrun_flag] = combined_version >= 519
|
31
26
|
config[:single_issuer_flag] = combined_version >= 600
|
27
|
+
config[:prep_bind] = combined_version >= 611
|
28
|
+
config[:prep_listen] = combined_version >= 611
|
32
29
|
|
33
30
|
config
|
34
31
|
end
|
@@ -54,16 +51,10 @@ if !find_library('uring', nil, File.join(liburing_path, 'src'))
|
|
54
51
|
raise "Couldn't find liburing.a"
|
55
52
|
end
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
$defs << '-DHAVE_IO_URING_PREP_MULTISHOT_ACCEPT' if config[:multishot_accept]
|
62
|
-
$defs << '-DHAVE_IO_URING_PREP_RECV_MULTISHOT' if config[:multishot_recv]
|
63
|
-
$defs << '-DHAVE_IO_URING_PREP_RECVMSG_MULTISHOT' if config[:multishot_recvmsg]
|
64
|
-
$defs << '-DHAVE_IO_URING_TIMEOUT_MULTISHOT' if config[:multishot_timeout]
|
65
|
-
$defs << '-DHAVE_IORING_SETUP_SUBMIT_ALL' if config[:submit_all_flag]
|
66
|
-
$defs << '-DHAVE_IORING_SETUP_COOP_TASKRUN' if config[:coop_taskrun_flag]
|
54
|
+
$defs << '-DHAVE_IORING_SETUP_SUBMIT_ALL' if config[:submit_all_flag]
|
55
|
+
$defs << '-DHAVE_IORING_SETUP_COOP_TASKRUN' if config[:coop_taskrun_flag]
|
56
|
+
$defs << '-DHAVE_IO_URING_PREP_BIND' if config[:prep_bind]
|
57
|
+
$defs << '-DHAVE_IO_URING_PREP_LISTEN' if config[:prep_listen]
|
67
58
|
$CFLAGS << ' -Wno-pointer-arith'
|
68
59
|
|
69
60
|
CONFIG['optflags'] << ' -fno-strict-aliasing'
|