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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -1
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +44 -1
  5. data/TODO.md +12 -3
  6. data/examples/bm_snooze.rb +89 -0
  7. data/examples/bm_sqlite.rb +89 -0
  8. data/examples/bm_write.rb +56 -0
  9. data/examples/dns_client.rb +12 -0
  10. data/examples/http_server.rb +42 -43
  11. data/examples/pg.rb +85 -0
  12. data/examples/server_client.rb +64 -0
  13. data/examples/snooze.rb +44 -0
  14. data/examples/stream.rb +85 -0
  15. data/examples/write_dev_null.rb +16 -0
  16. data/ext/um/extconf.rb +81 -14
  17. data/ext/um/um.c +468 -414
  18. data/ext/um/um.h +149 -40
  19. data/ext/um/um_async_op.c +40 -0
  20. data/ext/um/um_async_op_class.c +136 -0
  21. data/ext/um/um_buffer.c +49 -0
  22. data/ext/um/um_class.c +176 -44
  23. data/ext/um/um_const.c +174 -9
  24. data/ext/um/um_ext.c +8 -0
  25. data/ext/um/um_mutex_class.c +47 -0
  26. data/ext/um/um_op.c +89 -111
  27. data/ext/um/um_queue_class.c +58 -0
  28. data/ext/um/um_ssl.c +850 -0
  29. data/ext/um/um_ssl.h +22 -0
  30. data/ext/um/um_ssl_class.c +138 -0
  31. data/ext/um/um_sync.c +273 -0
  32. data/ext/um/um_utils.c +1 -1
  33. data/lib/uringmachine/dns_resolver.rb +84 -0
  34. data/lib/uringmachine/ssl/context_builder.rb +96 -0
  35. data/lib/uringmachine/ssl.rb +394 -0
  36. data/lib/uringmachine/version.rb +1 -1
  37. data/lib/uringmachine.rb +27 -3
  38. data/supressions/ruby.supp +71 -0
  39. data/test/helper.rb +6 -0
  40. data/test/test_async_op.rb +119 -0
  41. data/test/test_ssl.rb +155 -0
  42. data/test/test_um.rb +464 -47
  43. data/uringmachine.gemspec +3 -2
  44. data/vendor/liburing/.gitignore +5 -0
  45. data/vendor/liburing/CHANGELOG +1 -0
  46. data/vendor/liburing/configure +32 -0
  47. data/vendor/liburing/examples/Makefile +1 -0
  48. data/vendor/liburing/examples/reg-wait.c +159 -0
  49. data/vendor/liburing/liburing.spec +1 -1
  50. data/vendor/liburing/src/include/liburing/io_uring.h +48 -2
  51. data/vendor/liburing/src/include/liburing.h +28 -2
  52. data/vendor/liburing/src/int_flags.h +10 -3
  53. data/vendor/liburing/src/liburing-ffi.map +13 -2
  54. data/vendor/liburing/src/liburing.map +9 -0
  55. data/vendor/liburing/src/queue.c +25 -16
  56. data/vendor/liburing/src/register.c +73 -4
  57. data/vendor/liburing/src/setup.c +46 -18
  58. data/vendor/liburing/src/setup.h +6 -0
  59. data/vendor/liburing/test/Makefile +7 -0
  60. data/vendor/liburing/test/cmd-discard.c +427 -0
  61. data/vendor/liburing/test/fifo-nonblock-read.c +69 -0
  62. data/vendor/liburing/test/file-exit-unreg.c +48 -0
  63. data/vendor/liburing/test/io_uring_passthrough.c +2 -0
  64. data/vendor/liburing/test/io_uring_register.c +13 -2
  65. data/vendor/liburing/test/napi-test.c +1 -1
  66. data/vendor/liburing/test/no-mmap-inval.c +1 -1
  67. data/vendor/liburing/test/read-mshot-empty.c +2 -0
  68. data/vendor/liburing/test/read-mshot-stdin.c +121 -0
  69. data/vendor/liburing/test/read-mshot.c +6 -0
  70. data/vendor/liburing/test/recvsend_bundle.c +2 -2
  71. data/vendor/liburing/test/reg-fd-only.c +1 -1
  72. data/vendor/liburing/test/reg-wait.c +251 -0
  73. data/vendor/liburing/test/regbuf-clone.c +458 -0
  74. data/vendor/liburing/test/resize-rings.c +643 -0
  75. data/vendor/liburing/test/rsrc_tags.c +1 -1
  76. data/vendor/liburing/test/sqpoll-sleep.c +39 -8
  77. data/vendor/liburing/test/sqwait.c +136 -0
  78. data/vendor/liburing/test/sync-cancel.c +8 -1
  79. data/vendor/liburing/test/timeout.c +13 -8
  80. metadata +52 -8
  81. data/examples/http_server_multishot.rb +0 -57
  82. data/examples/http_server_simpler.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a9efd4af7de9fdb8db6888b9f6347e9dbc63aae9e26fa0f81d43984d265bd90
4
- data.tar.gz: 5289699ce1e3e173d3766e0211fc77b57f3c123e2a6e041e9e751a3bf5ce07b2
3
+ metadata.gz: 0b063db46fac29472c42866eb1864c731b24fc0846fe424eec06213bba8fde0a
4
+ data.tar.gz: a535fabf5d16107de8d3823766d9ff0d7d22d0702c5401e263eb925eb222dd5d
5
5
  SHA512:
6
- metadata.gz: 3cf02bbaae5a9e19b9ea994d1c2bf4b327a96121b083ca6756eca70629738f6c24aca10096bb49a0400678542232062969889ed3f0773591f5dc71f718707fef
7
- data.tar.gz: 67b368097ddecd6c9ca9ce360cc918d8e7de91ec83a00d617765c2026fbe08f16f5009b78323e9f314cd1df63a1baaa39e8887b4a90fff0cb8010d810276df7d
6
+ metadata.gz: 94ad9e942e84e87f0acbd75007962c3df5ea259de77ef4e590ff91dc4ea614dcd22e7db3da1954be05b11951e621f0cf58858391b677e109e97590bb0f8e3dee
7
+ data.tar.gz: 4c77e826fb33fc879de748b877eec9a6d49f6927b8167377b4191c30b9aa2b638dbd0cc76a77ec0a17daec648d2587ca4357f367501391aad06c7532606e85b2
@@ -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,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
- ## Example
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
- - queues
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
@@ -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,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, '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
- 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/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)"
@@ -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"