uringmachine 0.5 → 0.6

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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.5'
4
+ VERSION = '0.6'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -8,8 +8,12 @@ UM = UringMachine
8
8
  class UringMachine
9
9
  @@fiber_map = {}
10
10
 
11
- def spin(value = nil, &block)
12
- f = Fiber.new do |resume_value|
11
+ def fiber_map
12
+ @@fiber_map
13
+ end
14
+
15
+ def spin(value = nil, fiber_class = Fiber, &block)
16
+ f = fiber_class.new do |resume_value|
13
17
  block.(resume_value)
14
18
  rescue Exception => e
15
19
  STDERR.puts "Unhandled fiber exception: #{e.inspect}"
@@ -30,4 +34,8 @@ class UringMachine
30
34
  @resolver ||= DNSResolver.new(self)
31
35
  @resolver.resolve(hostname, type)
32
36
  end
37
+
38
+ def ssl_accept(fd, ssl_ctx)
39
+ SSL::Connection.new(self, fd, ssl_ctx)
40
+ end
33
41
  end
data/test/helper.rb CHANGED
@@ -51,6 +51,12 @@ module Minitest::Assertions
51
51
  end
52
52
 
53
53
  class UMBaseTest < Minitest::Test
54
+ # pull in UM constants
55
+ UM.constants.each do |c|
56
+ v = UM.const_get(c)
57
+ const_set(c, v) if v.is_a?(Integer)
58
+ end
59
+
54
60
  attr_accessor :machine
55
61
 
56
62
  def setup
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'socket'
5
+ require 'uringmachine/actor'
6
+
7
+ class ActorTest < UMBaseTest
8
+ module Counter
9
+ def setup
10
+ @count = 0
11
+ end
12
+
13
+ def incr
14
+ @count += 1
15
+ end
16
+
17
+ def get
18
+ @count
19
+ end
20
+
21
+ def reset
22
+ @count = 0
23
+ end
24
+ end
25
+
26
+ def test_basic_actor_functionality
27
+ actor = @machine.spin_actor(Counter)
28
+
29
+ assert_kind_of Fiber, actor
30
+
31
+ assert_equal 0, actor.call(:get)
32
+ assert_equal 1, actor.call(:incr)
33
+ assert_equal actor, actor.cast(:incr)
34
+ assert_equal 2, actor.call(:get)
35
+ assert_equal actor, actor.cast(:reset)
36
+ assert_equal 0, actor.call(:get)
37
+ end
38
+
39
+ module Counter2
40
+ def setup(count)
41
+ @count = count
42
+ end
43
+
44
+ def incr
45
+ @count += 1
46
+ end
47
+
48
+ def get
49
+ @count
50
+ end
51
+
52
+ def reset
53
+ @count = 0
54
+ end
55
+ end
56
+
57
+
58
+ def test_actor_with_args
59
+ actor = @machine.spin_actor(Counter2, 43)
60
+
61
+ assert_equal 43, actor.call(:get)
62
+ end
63
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'socket'
5
+
6
+ class AsyncOpTest < UMBaseTest
7
+ def setup
8
+ super
9
+ @t0 = monotonic_clock
10
+ @op = machine.prep_timeout(0.05)
11
+ end
12
+
13
+ def test_async_op_await
14
+ assert_equal 1, machine.pending_count
15
+ res = @op.await
16
+ t1 = monotonic_clock
17
+ assert_in_range 0.04..0.08, t1 - @t0
18
+ assert_equal 0, machine.pending_count
19
+ assert_equal (-ETIME), res
20
+ assert_equal true, @op.done?
21
+ assert_equal false, @op.cancelled?
22
+ end
23
+
24
+ def test_async_op_join
25
+ assert_equal 1, machine.pending_count
26
+ res = @op.join
27
+ t1 = monotonic_clock
28
+ assert_in_range 0.04..0.08, t1 - @t0
29
+ assert_equal 0, machine.pending_count
30
+ assert_equal (-ETIME), res
31
+ assert_equal true, @op.done?
32
+ assert_equal false, @op.cancelled?
33
+ end
34
+
35
+ def test_async_op_cancel
36
+ machine.sleep(0.01)
37
+ assert_equal 1, machine.pending_count
38
+ @op.cancel
39
+ assert_equal false, @op.done?
40
+
41
+ machine.sleep(0.01)
42
+
43
+ assert_equal 0, machine.pending_count
44
+ assert_equal true, @op.done?
45
+ assert_equal (-ECANCELED), @op.result
46
+ assert_equal true, @op.cancelled?
47
+ end
48
+
49
+ def test_async_op_await_with_cancel
50
+ machine.spin do
51
+ @op.cancel
52
+ end
53
+
54
+ res = @op.await
55
+
56
+ assert_equal 0, machine.pending_count
57
+ assert_equal true, @op.done?
58
+ assert_equal (-ECANCELED), res
59
+ assert_equal true, @op.cancelled?
60
+ end
61
+
62
+ class TOError < RuntimeError; end
63
+
64
+ def test_async_op_await_with_timeout
65
+ e = nil
66
+
67
+ begin
68
+ machine.timeout(0.01, TOError) do
69
+ @op.await
70
+ end
71
+ rescue => e
72
+ end
73
+
74
+ assert_equal 0, machine.pending_count
75
+ assert_kind_of TOError, e
76
+ assert_equal true, @op.done?
77
+ assert_equal (-ECANCELED), @op.result
78
+ assert_equal true, @op.cancelled?
79
+ end
80
+
81
+ def test_async_op_await_with_timeout2
82
+ e = nil
83
+
84
+ begin
85
+ machine.timeout(0.1, TOError) do
86
+ @op.await
87
+ end
88
+ rescue => e
89
+ end
90
+
91
+ # machine.timeout is cancelled async, so CQE is not yet reaped
92
+ assert_equal 1, machine.pending_count
93
+ assert_nil e
94
+ assert_equal true, @op.done?
95
+ assert_equal (-ETIME), @op.result
96
+ assert_equal false, @op.cancelled?
97
+
98
+ # wait for timeout cancellation
99
+ machine.sleep(0.01)
100
+ assert_equal 0, machine.pending_count
101
+ end
102
+ end
103
+
104
+ class PrepTimeoutTest < UMBaseTest
105
+ def test_prep_timeout
106
+ op = machine.prep_timeout(0.03)
107
+ assert_kind_of UM::AsyncOp, op
108
+ assert_equal :timeout, op.kind
109
+
110
+ assert_equal false, op.done?
111
+ assert_nil op.result
112
+
113
+ machine.sleep(0.05)
114
+
115
+ assert_equal true, op.done?
116
+ assert_equal (-ETIME), op.result
117
+ assert_equal false, op.cancelled?
118
+ end
119
+ end
data/test/test_ssl.rb ADDED
@@ -0,0 +1,155 @@
1
+ require_relative "helper"
2
+
3
+ __END__
4
+
5
+ require 'uringmachine/ssl'
6
+ require 'openssl'
7
+ require 'localhost'
8
+
9
+ class TestSSLContext < Minitest::Test
10
+
11
+ if false
12
+ def test_raises_with_invalid_keystore_file
13
+ ctx = UM::SSL::Context.new
14
+
15
+ exception = assert_raises(ArgumentError) { ctx.keystore = "/no/such/keystore" }
16
+ assert_equal("Keystore file '/no/such/keystore' does not exist", exception.message)
17
+ end
18
+
19
+ def test_raises_with_unreadable_keystore_file
20
+ ctx = UM::SSL::Context.new
21
+
22
+ File.stub(:exist?, true) do
23
+ File.stub(:readable?, false) do
24
+ exception = assert_raises(ArgumentError) { ctx.keystore = "/unreadable/keystore" }
25
+ assert_equal("Keystore file '/unreadable/keystore' is not readable", exception.message)
26
+ end
27
+ end
28
+ end
29
+ else
30
+ def test_raises_with_invalid_key_file
31
+ ctx = UM::SSL::Context.new
32
+
33
+ exception = assert_raises(ArgumentError) { ctx.key = "/no/such/key" }
34
+ assert_equal("Key file '/no/such/key' does not exist", exception.message)
35
+ end
36
+
37
+ def test_raises_with_unreadable_key_file
38
+ ctx = UM::SSL::Context.new
39
+
40
+ File.stub(:exist?, true) do
41
+ File.stub(:readable?, false) do
42
+ exception = assert_raises(ArgumentError) { ctx.key = "/unreadable/key" }
43
+ assert_equal("Key file '/unreadable/key' is not readable", exception.message)
44
+ end
45
+ end
46
+ end
47
+
48
+ def test_raises_with_invalid_cert_file
49
+ ctx = UM::SSL::Context.new
50
+
51
+ exception = assert_raises(ArgumentError) { ctx.cert = "/no/such/cert" }
52
+ assert_equal("Cert file '/no/such/cert' does not exist", exception.message)
53
+ end
54
+
55
+ def test_raises_with_unreadable_cert_file
56
+ ctx = UM::SSL::Context.new
57
+
58
+ File.stub(:exist?, true) do
59
+ File.stub(:readable?, false) do
60
+ exception = assert_raises(ArgumentError) { ctx.key = "/unreadable/cert" }
61
+ assert_equal("Key file '/unreadable/cert' is not readable", exception.message)
62
+ end
63
+ end
64
+ end
65
+
66
+ def test_raises_with_invalid_key_pem
67
+ ctx = UM::SSL::Context.new
68
+
69
+ exception = assert_raises(ArgumentError) { ctx.key_pem = nil }
70
+ assert_equal("'key_pem' is not a String", exception.message)
71
+ end
72
+
73
+ def test_raises_with_unreadable_ca_file
74
+ ctx = UM::SSL::Context.new
75
+
76
+ File.stub(:exist?, true) do
77
+ File.stub(:readable?, false) do
78
+ exception = assert_raises(ArgumentError) { ctx.ca = "/unreadable/cert" }
79
+ assert_equal("ca file '/unreadable/cert' is not readable", exception.message)
80
+ end
81
+ end
82
+ end
83
+
84
+ def test_raises_with_invalid_cert_pem
85
+ ctx = UM::SSL::Context.new
86
+
87
+ exception = assert_raises(ArgumentError) { ctx.cert_pem = nil }
88
+ assert_equal("'cert_pem' is not a String", exception.message)
89
+ end
90
+
91
+ def test_raises_with_invalid_key_password_command
92
+ ctx = UM::SSL::Context.new
93
+ ctx.key_password_command = '/unreadable/decrypt_command'
94
+
95
+ assert_raises(Errno::ENOENT) { ctx.key_password }
96
+ end
97
+ end
98
+ end
99
+
100
+ class TestSSLServer < UMBaseTest
101
+ def setup
102
+ super
103
+ @port = assign_port
104
+ @ssl_ctx = create_ssl_ctx
105
+ end
106
+
107
+ def create_ssl_ctx
108
+ authority = Localhost::Authority.fetch
109
+ authority.server_context
110
+ end
111
+
112
+ def test_ssl_accept
113
+ server_fd = machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
114
+ machine.bind(server_fd, '127.0.0.1', @port)
115
+ res = machine.listen(server_fd, 5)
116
+ assert_equal 0, res
117
+ assert_equal 0, machine.pending_count
118
+
119
+ fd = nil
120
+ sock = nil
121
+
122
+ reply = nil
123
+ t = Thread.new do
124
+ p 21
125
+ sleep 0.1
126
+ p 22
127
+ sock = TCPSocket.new('127.0.0.1', @port)
128
+ p 23
129
+ sleep 0.1
130
+ p 24
131
+ client = OpenSSL::SSL::SSLSocket.new(sock)
132
+ p 25
133
+ client.connect
134
+ p 26
135
+ client.write('foobar')
136
+ p 27
137
+ reply = client.read(8192)
138
+ p 28
139
+ end
140
+
141
+ p 11
142
+ fd = machine.accept(server_fd)
143
+ p 12
144
+ sock = machine.ssl_accept(fd, @ssl_ctx)
145
+ msg = +''
146
+ p 13
147
+ ret = sock.recv(msg, 8192)
148
+ p 14
149
+ sock.send("Hello: #{msg} (#{ret})", 0)
150
+ p 15
151
+ machine.close(fd)
152
+
153
+ assert_equal 'Hello: foobar (6)', reply
154
+ end
155
+ end
data/test/test_um.rb CHANGED
@@ -212,6 +212,57 @@ class SleepTest < UMBaseTest
212
212
  end
213
213
  end
214
214
 
215
+ class PeriodicallyTest < UMBaseTest
216
+ class Cancel < StandardError; end
217
+
218
+ def test_periodically
219
+ count = 0
220
+ cancel = 0
221
+
222
+ t0 = monotonic_clock
223
+ assert_equal 0, machine.pending_count
224
+ begin
225
+ machine.periodically(0.01) do
226
+ count += 1
227
+ raise Cancel if count >= 5
228
+ end
229
+ rescue Cancel
230
+ cancel = 1
231
+ end
232
+ machine.snooze
233
+ assert_equal 0, machine.pending_count
234
+ t1 = monotonic_clock
235
+ assert_in_range 0.05..0.09, t1 - t0
236
+ assert_equal 5, count
237
+ assert_equal 1, cancel
238
+ end
239
+
240
+ def test_periodically_with_timeout
241
+ count = 0
242
+ cancel = 0
243
+
244
+ t0 = monotonic_clock
245
+ assert_equal 0, machine.pending_count
246
+ begin
247
+ machine.timeout(0.05, Cancel) do
248
+ machine.periodically(0.01) do
249
+ count += 1
250
+ raise Cancel if count >= 5
251
+ end
252
+ end
253
+ rescue Cancel
254
+ cancel = 1
255
+ end
256
+ machine.snooze
257
+ assert_equal 0, machine.pending_count
258
+ t1 = monotonic_clock
259
+ assert_in_range 0.05..0.08, t1 - t0
260
+ assert_in_range 4..6, count
261
+ assert_equal 1, cancel
262
+
263
+ end
264
+ end
265
+
215
266
  class ReadTest < UMBaseTest
216
267
  def test_read
217
268
  r, w = IO.pipe
@@ -274,6 +325,26 @@ class ReadTest < UMBaseTest
274
325
  assert_equal 3, result
275
326
  assert_equal 'foobar', buffer
276
327
  end
328
+
329
+ def test_read_with_string_io
330
+ require 'stringio'
331
+
332
+ buffer = +'foo'
333
+ sio = StringIO.new(buffer)
334
+
335
+ r, w = IO.pipe
336
+ w << 'bar'
337
+
338
+ result = machine.read(r.fileno, buffer, 100, -1)
339
+ assert_equal 3, result
340
+ assert_equal 'foobar', sio.read
341
+
342
+ w << 'baz'
343
+
344
+ result = machine.read(r.fileno, buffer, 100, -1)
345
+ assert_equal 3, result
346
+ assert_equal 'baz', sio.read
347
+ end
277
348
  end
278
349
 
279
350
  class ReadEachTest < UMBaseTest
@@ -1048,6 +1119,4 @@ class WaitTest < UMBaseTest
1048
1119
 
1049
1120
  assert_raises(Errno::ECHILD) { machine.waitpid(1, UM::WEXITED) }
1050
1121
  end
1051
-
1052
1122
  end
1053
-
data/uringmachine.gemspec CHANGED
@@ -20,8 +20,9 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
  s.required_ruby_version = '>= 3.3'
22
22
 
23
- s.add_development_dependency 'rake-compiler', '1.2.7'
24
- s.add_development_dependency 'minitest', '5.25.1'
23
+ s.add_development_dependency 'rake-compiler', '1.2.9'
24
+ s.add_development_dependency 'minitest', '5.25.4'
25
25
  s.add_development_dependency 'http_parser.rb', '0.8.0'
26
- s.add_development_dependency 'benchmark-ips', '2.10.0'
26
+ s.add_development_dependency 'benchmark-ips', '2.14.0'
27
+ s.add_development_dependency 'localhost', '1.3.1'
27
28
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uringmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: '0.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-14 00:00:00.000000000 Z
10
+ date: 2025-04-23 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake-compiler
@@ -16,28 +15,28 @@ dependencies:
16
15
  requirements:
17
16
  - - '='
18
17
  - !ruby/object:Gem::Version
19
- version: 1.2.7
18
+ version: 1.2.9
20
19
  type: :development
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - '='
25
24
  - !ruby/object:Gem::Version
26
- version: 1.2.7
25
+ version: 1.2.9
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: minitest
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - '='
32
31
  - !ruby/object:Gem::Version
33
- version: 5.25.1
32
+ version: 5.25.4
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - '='
39
38
  - !ruby/object:Gem::Version
40
- version: 5.25.1
39
+ version: 5.25.4
41
40
  - !ruby/object:Gem::Dependency
42
41
  name: http_parser.rb
43
42
  requirement: !ruby/object:Gem::Requirement
@@ -58,15 +57,28 @@ dependencies:
58
57
  requirements:
59
58
  - - '='
60
59
  - !ruby/object:Gem::Version
61
- version: 2.10.0
60
+ version: 2.14.0
62
61
  type: :development
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - '='
67
66
  - !ruby/object:Gem::Version
68
- version: 2.10.0
69
- description:
67
+ version: 2.14.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: localhost
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '='
73
+ - !ruby/object:Gem::Version
74
+ version: 1.3.1
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '='
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.1
70
82
  email: sharon@noteflakes.com
71
83
  executables: []
72
84
  extensions:
@@ -84,19 +96,26 @@ files:
84
96
  - README.md
85
97
  - Rakefile
86
98
  - TODO.md
99
+ - examples/bm_http_parse.rb
100
+ - examples/bm_queue.rb
87
101
  - examples/bm_snooze.rb
102
+ - examples/bm_sqlite.rb
88
103
  - examples/bm_write.rb
89
104
  - examples/dns_client.rb
90
105
  - examples/echo_server.rb
91
106
  - examples/http_server.rb
92
107
  - examples/inout.rb
93
108
  - examples/nc.rb
109
+ - examples/pg.rb
94
110
  - examples/server_client.rb
95
111
  - examples/snooze.rb
112
+ - examples/stream.rb
96
113
  - examples/write_dev_null.rb
97
114
  - ext/um/extconf.rb
98
115
  - ext/um/um.c
99
116
  - ext/um/um.h
117
+ - ext/um/um_async_op.c
118
+ - ext/um/um_async_op_class.c
100
119
  - ext/um/um_buffer.c
101
120
  - ext/um/um_class.c
102
121
  - ext/um/um_const.c
@@ -104,13 +123,22 @@ files:
104
123
  - ext/um/um_mutex_class.c
105
124
  - ext/um/um_op.c
106
125
  - ext/um/um_queue_class.c
126
+ - ext/um/um_ssl.c
127
+ - ext/um/um_ssl.h
128
+ - ext/um/um_ssl_class.c
107
129
  - ext/um/um_sync.c
108
130
  - ext/um/um_utils.c
109
131
  - lib/uringmachine.rb
132
+ - lib/uringmachine/actor.rb
110
133
  - lib/uringmachine/dns_resolver.rb
134
+ - lib/uringmachine/ssl.rb
135
+ - lib/uringmachine/ssl/context_builder.rb
111
136
  - lib/uringmachine/version.rb
112
137
  - supressions/ruby.supp
113
138
  - test/helper.rb
139
+ - test/test_actor.rb
140
+ - test/test_async_op.rb
141
+ - test/test_ssl.rb
114
142
  - test/test_um.rb
115
143
  - uringmachine.gemspec
116
144
  - vendor/liburing/.github/actions/codespell/stopwords
@@ -413,7 +441,6 @@ metadata:
413
441
  source_code_uri: https://github.com/digital-fabric/uringmachine
414
442
  documentation_uri: https://www.rubydoc.info/gems/uringmachine
415
443
  changelog_uri: https://github.com/digital-fabric/uringmachine/blob/master/CHANGELOG.md
416
- post_install_message:
417
444
  rdoc_options:
418
445
  - "--title"
419
446
  - UringMachine
@@ -432,8 +459,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
432
459
  - !ruby/object:Gem::Version
433
460
  version: '0'
434
461
  requirements: []
435
- rubygems_version: 3.5.16
436
- signing_key:
462
+ rubygems_version: 3.6.2
437
463
  specification_version: 4
438
464
  summary: A lean, mean io_uring machine
439
465
  test_files: []