uringmachine 0.4 → 0.5.1
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 +16 -0
- data/README.md +44 -1
- data/TODO.md +12 -3
- data/examples/bm_snooze.rb +89 -0
- data/examples/bm_sqlite.rb +89 -0
- data/examples/bm_write.rb +56 -0
- data/examples/dns_client.rb +12 -0
- data/examples/http_server.rb +42 -43
- data/examples/pg.rb +85 -0
- data/examples/server_client.rb +64 -0
- data/examples/snooze.rb +44 -0
- data/examples/stream.rb +85 -0
- data/examples/write_dev_null.rb +16 -0
- data/ext/um/extconf.rb +81 -14
- data/ext/um/um.c +468 -414
- data/ext/um/um.h +149 -40
- data/ext/um/um_async_op.c +40 -0
- data/ext/um/um_async_op_class.c +136 -0
- data/ext/um/um_buffer.c +49 -0
- data/ext/um/um_class.c +176 -44
- data/ext/um/um_const.c +174 -9
- data/ext/um/um_ext.c +8 -0
- data/ext/um/um_mutex_class.c +47 -0
- data/ext/um/um_op.c +89 -111
- data/ext/um/um_queue_class.c +58 -0
- data/ext/um/um_ssl.c +850 -0
- data/ext/um/um_ssl.h +22 -0
- data/ext/um/um_ssl_class.c +138 -0
- data/ext/um/um_sync.c +273 -0
- data/ext/um/um_utils.c +1 -1
- data/lib/uringmachine/dns_resolver.rb +84 -0
- data/lib/uringmachine/ssl/context_builder.rb +96 -0
- data/lib/uringmachine/ssl.rb +394 -0
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +27 -3
- data/supressions/ruby.supp +71 -0
- data/test/helper.rb +6 -0
- data/test/test_async_op.rb +119 -0
- data/test/test_ssl.rb +155 -0
- data/test/test_um.rb +464 -47
- data/uringmachine.gemspec +3 -2
- data/vendor/liburing/.gitignore +5 -0
- data/vendor/liburing/CHANGELOG +1 -0
- data/vendor/liburing/configure +32 -0
- data/vendor/liburing/examples/Makefile +1 -0
- data/vendor/liburing/examples/reg-wait.c +159 -0
- data/vendor/liburing/liburing.spec +1 -1
- data/vendor/liburing/src/include/liburing/io_uring.h +48 -2
- data/vendor/liburing/src/include/liburing.h +28 -2
- data/vendor/liburing/src/int_flags.h +10 -3
- data/vendor/liburing/src/liburing-ffi.map +13 -2
- data/vendor/liburing/src/liburing.map +9 -0
- data/vendor/liburing/src/queue.c +25 -16
- data/vendor/liburing/src/register.c +73 -4
- data/vendor/liburing/src/setup.c +46 -18
- data/vendor/liburing/src/setup.h +6 -0
- data/vendor/liburing/test/Makefile +7 -0
- data/vendor/liburing/test/cmd-discard.c +427 -0
- data/vendor/liburing/test/fifo-nonblock-read.c +69 -0
- data/vendor/liburing/test/file-exit-unreg.c +48 -0
- data/vendor/liburing/test/io_uring_passthrough.c +2 -0
- data/vendor/liburing/test/io_uring_register.c +13 -2
- data/vendor/liburing/test/napi-test.c +1 -1
- data/vendor/liburing/test/no-mmap-inval.c +1 -1
- data/vendor/liburing/test/read-mshot-empty.c +2 -0
- data/vendor/liburing/test/read-mshot-stdin.c +121 -0
- data/vendor/liburing/test/read-mshot.c +6 -0
- data/vendor/liburing/test/recvsend_bundle.c +2 -2
- data/vendor/liburing/test/reg-fd-only.c +1 -1
- data/vendor/liburing/test/reg-wait.c +251 -0
- data/vendor/liburing/test/regbuf-clone.c +458 -0
- data/vendor/liburing/test/resize-rings.c +643 -0
- data/vendor/liburing/test/rsrc_tags.c +1 -1
- data/vendor/liburing/test/sqpoll-sleep.c +39 -8
- data/vendor/liburing/test/sqwait.c +136 -0
- data/vendor/liburing/test/sync-cancel.c +8 -1
- data/vendor/liburing/test/timeout.c +13 -8
- metadata +52 -8
- 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: 0b063db46fac29472c42866eb1864c731b24fc0846fe424eec06213bba8fde0a
|
4
|
+
data.tar.gz: a535fabf5d16107de8d3823766d9ff0d7d22d0702c5401e263eb925eb222dd5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94ad9e942e84e87f0acbd75007962c3df5ea259de77ef4e590ff91dc4ea614dcd22e7db3da1954be05b11951e621f0cf58858391b677e109e97590bb0f8e3dee
|
7
|
+
data.tar.gz: 4c77e826fb33fc879de748b877eec9a6d49f6927b8167377b4191c30b9aa2b638dbd0cc76a77ec0a17daec648d2587ca4357f367501391aad06c7532606e85b2
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
- Add `#prep_timeout` and `AsyncOp`
|
2
|
+
|
3
|
+
# 2024-11-14 Version 0.5
|
4
|
+
|
5
|
+
- Add `#waitpid`
|
6
|
+
- Add `UM.pipe`, `UM.kernel_version`
|
7
|
+
- Add `#open`
|
8
|
+
- Fix `#spin`
|
9
|
+
- Fix handling of signal interruption.
|
10
|
+
- Reimplement and simplify um_op
|
11
|
+
- Add `UM::Queue`, `#push`, `#pop`, `#shift`, `#unshift`
|
12
|
+
- Add `UM::Mutex`, `#synchronize`
|
13
|
+
- Add `#recv_each`
|
14
|
+
- Add `#getsockopt`, `#setsockopt`
|
15
|
+
- Simplify and improve op management
|
16
|
+
|
1
17
|
# 2024-10-06 Version 0.4
|
2
18
|
|
3
19
|
- Add socket constants
|
data/README.md
CHANGED
@@ -32,6 +32,8 @@ UringMachine is based on my experience marrying Ruby and io_uring:
|
|
32
32
|
- [IOU](https://github.com/digital-fabric/iou) - a low-level asynchronous API
|
33
33
|
for using io_uring from Ruby.
|
34
34
|
|
35
|
+
### Learnings
|
36
|
+
|
35
37
|
Some important learnings from those two projects, in no particular order:
|
36
38
|
|
37
39
|
- Monkey-patching is not a good solution, long term. You need to deal with
|
@@ -73,7 +75,25 @@ based on the following principles:
|
|
73
75
|
- Do not insist on structured concurrency, just provide the APIs necessary to
|
74
76
|
create actors and to supervise the execution of fibers.
|
75
77
|
|
76
|
-
|
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
|
77
97
|
|
78
98
|
```ruby
|
79
99
|
require 'uringmachine'
|
@@ -94,3 +114,26 @@ loop do
|
|
94
114
|
end
|
95
115
|
end
|
96
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
@@ -1,5 +1,14 @@
|
|
1
|
-
- splice
|
1
|
+
- splice / - tee
|
2
2
|
- sendto
|
3
3
|
- recvfrom
|
4
|
-
|
5
|
-
-
|
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,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 'extralite'
|
9
|
+
gem 'benchmark-ips'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'uringmachine'
|
13
|
+
require 'extralite'
|
14
|
+
|
15
|
+
class UM::Actor < Fiber
|
16
|
+
def initialize(machine, target)
|
17
|
+
@machine = machine
|
18
|
+
@target = target
|
19
|
+
@mailbox = UM::Queue.new
|
20
|
+
super { act }
|
21
|
+
end
|
22
|
+
|
23
|
+
def act
|
24
|
+
while (sym, a, k, peer = @machine.shift(@mailbox))
|
25
|
+
|
26
|
+
begin
|
27
|
+
ret = @target.send(sym, *a, **k)
|
28
|
+
@machine.schedule(peer, ret)
|
29
|
+
rescue => e
|
30
|
+
@machine.schedule(peer, e)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue Exception => e
|
34
|
+
# handle unhandled exceptions
|
35
|
+
ensure
|
36
|
+
@machine.fiber_map.delete(self)
|
37
|
+
@machine.yield
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(sym, *a, **k)
|
41
|
+
@machine.push(@mailbox, [sym, a, k, Fiber.current])
|
42
|
+
ret = @machine.yield
|
43
|
+
raise(ret) if ret.is_a?(Exception)
|
44
|
+
ret
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class UM
|
49
|
+
def spin_actor(target)
|
50
|
+
f = UM::Actor.new(self, target)
|
51
|
+
schedule(f, nil)
|
52
|
+
@@fiber_map[f] = true
|
53
|
+
f
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Locker
|
58
|
+
def initialize(machine, target)
|
59
|
+
@machine = machine
|
60
|
+
@target = target
|
61
|
+
@mutex = UM::Mutex.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(sym, *a, **k)
|
65
|
+
@machine.synchronize(@mutex) { @target.send(sym, *a, **k) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
PATH = '/tmp/foo'
|
71
|
+
|
72
|
+
$machine = UM.new
|
73
|
+
$raw_db = Extralite::Database.new(PATH)
|
74
|
+
$actor_db = $machine.spin_actor(Extralite::Database.new(PATH))
|
75
|
+
$locker_db = Locker.new($machine, Extralite::Database.new(PATH))
|
76
|
+
|
77
|
+
[$raw_db, $actor_db, $locker_db].each do |db|
|
78
|
+
p db.query('select 1')
|
79
|
+
end
|
80
|
+
|
81
|
+
bm = Benchmark.ips do |x|
|
82
|
+
x.config(:time => 5, :warmup => 2)
|
83
|
+
|
84
|
+
x.report("raw") { $raw_db.query('select 1') }
|
85
|
+
x.report("actor") { $actor_db.query('select 1') }
|
86
|
+
x.report("locker") { $locker_db.query('select 1') }
|
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/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, '0.0.0.0', 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/pg.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/uringmachine'
|
4
|
+
|
5
|
+
@machine = UM.new
|
6
|
+
|
7
|
+
class UM::Stream
|
8
|
+
def initialize(machine, fd)
|
9
|
+
@machine, @fd, @bgid = machine, fd
|
10
|
+
@buffer = +''
|
11
|
+
@ofs_head = 0
|
12
|
+
@ofs_tail = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def feed
|
16
|
+
if (@ofs_head == @ofs_tail) && (@ofs_head >= 4096)
|
17
|
+
@buffer = +''
|
18
|
+
@ofs_head = @ofs_tail = 0
|
19
|
+
end
|
20
|
+
ret = @machine.read(@fd, @buffer, 65536, @ofs_tail)
|
21
|
+
if ret == 0
|
22
|
+
@eof = true
|
23
|
+
return false
|
24
|
+
end
|
25
|
+
|
26
|
+
@ofs_tail += ret
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def read(len)
|
31
|
+
if @ofs_head + len > @ofs_tail
|
32
|
+
feed
|
33
|
+
end
|
34
|
+
|
35
|
+
str = @buffer[@ofs_head, len]
|
36
|
+
@ofs_head += str.bytesize
|
37
|
+
str
|
38
|
+
end
|
39
|
+
|
40
|
+
def gets(sep = $/, _limit = nil, _chomp: nil)
|
41
|
+
if sep.is_a?(Integer)
|
42
|
+
sep = $/
|
43
|
+
_limit = sep
|
44
|
+
end
|
45
|
+
sep_size = sep.bytesize
|
46
|
+
|
47
|
+
while true
|
48
|
+
idx = @buffer.index(sep, @ofs_head)
|
49
|
+
if idx
|
50
|
+
str = @buffer[@ofs_head, idx + sep_size]
|
51
|
+
@ofs_head += str.bytesize
|
52
|
+
return str
|
53
|
+
end
|
54
|
+
|
55
|
+
return nil if !feed
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
$machine = UringMachine.new
|
61
|
+
|
62
|
+
server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
63
|
+
$machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
64
|
+
$machine.bind(server_fd, '127.0.0.1', 1234)
|
65
|
+
$machine.listen(server_fd, UM::SOMAXCONN)
|
66
|
+
puts 'Listening on port 1234'
|
67
|
+
|
68
|
+
def handle_connection(fd)
|
69
|
+
stream = UM::Stream.new($machine, fd)
|
70
|
+
|
71
|
+
while (l = stream.gets)
|
72
|
+
$machine.write(fd, "You said: #{l}")
|
73
|
+
end
|
74
|
+
rescue Exception => e
|
75
|
+
puts "Got error #{e.inspect}, closing connection"
|
76
|
+
$machine.close(fd) rescue nil
|
77
|
+
end
|
78
|
+
|
79
|
+
main = Fiber.current
|
80
|
+
trap('SIGINT') { $machine.spin { $machine.schedule(main, SystemExit.new) } }
|
81
|
+
|
82
|
+
$machine.accept_each(server_fd) do |fd|
|
83
|
+
puts "Connection accepted fd #{fd}"
|
84
|
+
$machine.spin(fd) { handle_connection(_1) }
|
85
|
+
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"
|