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
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
5
+ # use require, see https://github.com/puma/puma/pull/2381
6
+ # require 'puma/puma_http11'
7
+
8
+ require_relative '../uringmachine'
9
+
10
+ class UringMachine
11
+ module SSL
12
+ # Define constant at runtime, as it's easy to determine at built time,
13
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
14
+ # @version 5.0.0
15
+ HAS_TLS1_3 =
16
+ ((OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
17
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1)
18
+
19
+ class Socket
20
+ def initialize(socket, engine)
21
+ @socket = socket
22
+ @engine = engine
23
+ @peercert = nil
24
+ @reuse = nil
25
+ end
26
+
27
+ # @!attribute [r] to_io
28
+ def to_io
29
+ @socket
30
+ end
31
+
32
+ def closed?
33
+ @socket.closed?
34
+ end
35
+
36
+ # Returns a two element array,
37
+ # first is protocol version (SSL_get_version),
38
+ # second is 'handshake' state (SSL_state_string)
39
+ #
40
+ # Used for dropping tcp connections to ssl.
41
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
42
+ # @!attribute [r] ssl_version_state
43
+ # @version 5.0.0
44
+ #
45
+ def ssl_version_state
46
+ @engine.ssl_vers_st
47
+ end
48
+
49
+ # Used to check the handshake status, in particular when a TCP connection
50
+ # is made with TLSv1.3 as an available protocol
51
+ # @version 5.0.0
52
+ def bad_tlsv1_3?
53
+ HAS_TLS1_3 && ssl_version_state == ['TLSv1.3', 'SSLERR']
54
+ end
55
+ private :bad_tlsv1_3?
56
+
57
+ def readpartial(size)
58
+ while true
59
+ output = @engine.read
60
+ return output if output
61
+
62
+ data = @socket.readpartial(size)
63
+ @engine.inject(data)
64
+ output = @engine.read
65
+
66
+ return output if output
67
+
68
+ while neg_data = @engine.extract
69
+ @socket.write neg_data
70
+ end
71
+ end
72
+ end
73
+
74
+ def engine_read_all
75
+ output = @engine.read
76
+ while output and additional_output = @engine.read
77
+ output << additional_output
78
+ end
79
+ output
80
+ end
81
+
82
+ def read_nonblock(size, *_)
83
+ # *_ is to deal with keyword args that were added
84
+ # at some point (and being used in the wild)
85
+ while true
86
+ output = engine_read_all
87
+ return output if output
88
+
89
+ data = @socket.read_nonblock(size, exception: false)
90
+ if data == :wait_readable || data == :wait_writable
91
+ # It would make more sense to let @socket.read_nonblock raise
92
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
93
+ # I don't have a Windows machine to debug this so I can't explain
94
+ # exactly whats happening in that OS. Please let me know if you
95
+ # find out!
96
+ #
97
+ # In the meantime, we can emulate the correct behavior by
98
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
99
+ # ourselves.
100
+ raise IO::EAGAINWaitReadable
101
+ elsif data.nil?
102
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
103
+ return nil
104
+ end
105
+
106
+ @engine.inject(data)
107
+ output = engine_read_all
108
+
109
+ return output if output
110
+
111
+ while neg_data = @engine.extract
112
+ @socket.write neg_data
113
+ end
114
+ end
115
+ end
116
+
117
+ def write(data)
118
+ return 0 if data.empty?
119
+
120
+ data_size = data.bytesize
121
+ need = data_size
122
+
123
+ while true
124
+ wrote = @engine.write data
125
+
126
+ enc_wr = +''
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
129
+ end
130
+ @socket.write enc_wr unless enc_wr.empty?
131
+
132
+ need -= wrote
133
+
134
+ return data_size if need == 0
135
+
136
+ data = data.byteslice(wrote..-1)
137
+ end
138
+ end
139
+
140
+ alias_method :syswrite, :write
141
+ alias_method :<<, :write
142
+
143
+ # This is a temporary fix to deal with websockets code using
144
+ # write_nonblock.
145
+
146
+ # The problem with implementing it properly
147
+ # is that it means we'd have to have the ability to rewind
148
+ # an engine because after we write+extract, the socket
149
+ # write_nonblock call might raise an exception and later
150
+ # code would pass the same data in, but the engine would think
151
+ # it had already written the data in.
152
+ #
153
+ # So for the time being (and since write blocking is quite rare),
154
+ # go ahead and actually block in write_nonblock.
155
+ #
156
+ def write_nonblock(data, *_)
157
+ write data
158
+ end
159
+
160
+ def flush
161
+ @socket.flush
162
+ end
163
+
164
+ def close
165
+ begin
166
+ unless @engine.shutdown
167
+ while alert_data = @engine.extract
168
+ @socket.write alert_data
169
+ end
170
+ end
171
+ rescue IOError, SystemCallError
172
+ Puma::Util.purge_interrupt_queue
173
+ # nothing
174
+ ensure
175
+ @socket.close
176
+ end
177
+ end
178
+
179
+ # @!attribute [r] peeraddr
180
+ def peeraddr
181
+ @socket.peeraddr
182
+ end
183
+
184
+ # OpenSSL is loaded in `MicroSSL::ContextBuilder` when
185
+ # `MicroSSL::Context#verify_mode` is not `VERIFY_NONE`.
186
+ # When `VERIFY_NONE`, `MicroSSL::Engine#peercert` is nil, regardless of
187
+ # whether the client sends a cert.
188
+ # @return [OpenSSL::X509::Certificate, nil]
189
+ # @!attribute [r] peercert
190
+ def peercert
191
+ return @peercert if @peercert
192
+
193
+ raw = @engine.peercert
194
+ return nil unless raw
195
+
196
+ @peercert = OpenSSL::X509::Certificate.new raw
197
+ end
198
+ end
199
+
200
+ class Context
201
+ attr_accessor :verify_mode
202
+ attr_reader :no_tlsv1, :no_tlsv1_1
203
+
204
+ def initialize
205
+ @no_tlsv1 = false
206
+ @no_tlsv1_1 = false
207
+ @key = nil
208
+ @cert = nil
209
+ @key_pem = nil
210
+ @cert_pem = nil
211
+ @reuse = nil
212
+ @reuse_cache_size = nil
213
+ @reuse_timeout = nil
214
+ end
215
+
216
+ def check_file(file, desc)
217
+ raise ArgumentError, "#{desc} file '#{file}' does not exist" unless File.exist? file
218
+ raise ArgumentError, "#{desc} file '#{file}' is not readable" unless File.readable? file
219
+ end
220
+
221
+ # non-jruby Context properties
222
+ attr_reader :key
223
+ attr_reader :key_password_command
224
+ attr_reader :cert
225
+ attr_reader :ca
226
+ attr_reader :cert_pem
227
+ attr_reader :key_pem
228
+ attr_accessor :ssl_cipher_filter
229
+ attr_accessor :ssl_ciphersuites
230
+ attr_accessor :verification_flags
231
+
232
+ attr_reader :reuse, :reuse_cache_size, :reuse_timeout
233
+
234
+ def key=(key)
235
+ check_file key, 'Key'
236
+ @key = key
237
+ end
238
+
239
+ def key_password_command=(key_password_command)
240
+ @key_password_command = key_password_command
241
+ end
242
+
243
+ def cert=(cert)
244
+ check_file cert, 'Cert'
245
+ @cert = cert
246
+ end
247
+
248
+ def ca=(ca)
249
+ check_file ca, 'ca'
250
+ @ca = ca
251
+ end
252
+
253
+ def cert_pem=(cert_pem)
254
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
255
+ @cert_pem = cert_pem
256
+ end
257
+
258
+ def key_pem=(key_pem)
259
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
260
+ @key_pem = key_pem
261
+ end
262
+
263
+ def check
264
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
265
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
266
+ end
267
+
268
+ # Executes the command to return the password needed to decrypt the key.
269
+ def key_password
270
+ raise "Key password command not configured" if @key_password_command.nil?
271
+
272
+ stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
273
+
274
+ return stdout_str.chomp if status.success?
275
+
276
+ raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
277
+ end
278
+
279
+ # Controls session reuse. Allowed values are as follows:
280
+ # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
281
+ # in case reuse 'on' is made the default in future Puma versions.
282
+ # * 'dflt' - sets session reuse on, with OpenSSL default cache size of
283
+ # 20k and default timeout of 300 seconds.
284
+ # * 's,t' - where s and t are integer strings, for size and timeout.
285
+ # * 's' - where s is an integer strings for size.
286
+ # * ',t' - where t is an integer strings for timeout.
287
+ #
288
+ def reuse=(reuse_str)
289
+ case reuse_str
290
+ when 'off'
291
+ @reuse = nil
292
+ when 'dflt'
293
+ @reuse = true
294
+ when /\A\d+\z/
295
+ @reuse = true
296
+ @reuse_cache_size = reuse_str.to_i
297
+ when /\A\d+,\d+\z/
298
+ @reuse = true
299
+ size, time = reuse_str.split ','
300
+ @reuse_cache_size = size.to_i
301
+ @reuse_timeout = time.to_i
302
+ when /\A,\d+\z/
303
+ @reuse = true
304
+ @reuse_timeout = reuse_str.delete(',').to_i
305
+ end
306
+ end
307
+ end
308
+
309
+ # disables TLSv1
310
+ # @!attribute [w] no_tlsv1=
311
+ def no_tlsv1=(tlsv1)
312
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
313
+ @no_tlsv1 = tlsv1
314
+ end
315
+
316
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
317
+ # @!attribute [w] no_tlsv1_1=
318
+ def no_tlsv1_1=(tlsv1_1)
319
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
320
+ @no_tlsv1_1 = tlsv1_1
321
+ end
322
+
323
+ VERIFY_NONE = 0
324
+ VERIFY_PEER = 1
325
+ VERIFY_FAIL_IF_NO_PEER_CERT = 2
326
+
327
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
328
+ # /* Certificate verify flags */
329
+ VERIFICATION_FLAGS = {
330
+ "USE_CHECK_TIME" => 0x2,
331
+ "CRL_CHECK" => 0x4,
332
+ "CRL_CHECK_ALL" => 0x8,
333
+ "IGNORE_CRITICAL" => 0x10,
334
+ "X509_STRICT" => 0x20,
335
+ "ALLOW_PROXY_CERTS" => 0x40,
336
+ "POLICY_CHECK" => 0x80,
337
+ "EXPLICIT_POLICY" => 0x100,
338
+ "INHIBIT_ANY" => 0x200,
339
+ "INHIBIT_MAP" => 0x400,
340
+ "NOTIFY_POLICY" => 0x800,
341
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
342
+ "USE_DELTAS" => 0x2000,
343
+ "CHECK_SS_SIGNATURE" => 0x4000,
344
+ "TRUSTED_FIRST" => 0x8000,
345
+ "SUITEB_128_LOS_ONLY" => 0x10000,
346
+ "SUITEB_192_LOS" => 0x20000,
347
+ "SUITEB_128_LOS" => 0x30000,
348
+ "PARTIAL_CHAIN" => 0x80000,
349
+ "NO_ALT_CHAINS" => 0x100000,
350
+ "NO_CHECK_TIME" => 0x200000
351
+ }.freeze
352
+
353
+ class Server
354
+ def initialize(socket, ctx)
355
+ @socket = socket
356
+ @ctx = ctx
357
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
358
+ end
359
+
360
+ def accept
361
+ @ctx.check
362
+ io = @socket.accept
363
+ engine = Engine.server @eng_ctx
364
+ Socket.new io, engine
365
+ end
366
+
367
+ def accept_nonblock
368
+ @ctx.check
369
+ io = @socket.accept_nonblock
370
+ engine = Engine.server @eng_ctx
371
+ Socket.new io, engine
372
+ end
373
+
374
+ # @!attribute [r] to_io
375
+ def to_io
376
+ @socket
377
+ end
378
+
379
+ # @!attribute [r] addr
380
+ # @version 5.0.0
381
+ def addr
382
+ @socket.addr
383
+ end
384
+
385
+ def close
386
+ @socket.close unless @socket.closed? # closed? call is for Windows
387
+ end
388
+
389
+ def closed?
390
+ @socket.closed?
391
+ end
392
+ end
393
+ end
394
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.4'
4
+ VERSION = '0.5.1'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -1,17 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative './um_ext'
4
+ require_relative 'uringmachine/dns_resolver'
4
5
 
5
6
  UM = UringMachine
6
7
 
7
8
  class UringMachine
9
+ @@fiber_map = {}
10
+
11
+ def fiber_map
12
+ @@fiber_map
13
+ end
14
+
8
15
  def spin(value = nil, &block)
9
- Fiber.new do |resume_value|
16
+ f = Fiber.new do |resume_value|
10
17
  block.(resume_value)
11
18
  rescue Exception => e
12
- raise RuntimeError, "Unhandled fiber exception: #{e.inspect}"
19
+ STDERR.puts "Unhandled fiber exception: #{e.inspect}"
20
+ STDERR.puts e.backtrace.join("\n")
21
+ exit
13
22
  ensure
23
+ @@fiber_map.delete(f)
24
+ # yield control
14
25
  self.yield
15
- end.tap { |f| schedule(f, value) }
26
+ p :bad_bad_bad
27
+ end
28
+ schedule(f, value)
29
+ @@fiber_map[f] = true
30
+ f
31
+ end
32
+
33
+ def resolve(hostname, type = :A)
34
+ @resolver ||= DNSResolver.new(self)
35
+ @resolver.resolve(hostname, type)
36
+ end
37
+
38
+ def ssl_accept(fd, ssl_ctx)
39
+ SSL::Connection.new(self, fd, ssl_ctx)
16
40
  end
17
41
  end
@@ -0,0 +1,71 @@
1
+ {
2
+ On platforms where memcpy is safe for overlapped memory, the compiler will sometimes replace memmove with memcpy. Valgrind may report a false positive.
3
+ Memcheck:Overlap
4
+ fun:__memcpy_chk
5
+ fun:memmove
6
+ ...
7
+ }
8
+ {
9
+ Requiring a file will add it to the loaded features, which may be reported as a leak.
10
+ Memcheck:Leak
11
+ ...
12
+ fun:require_internal
13
+ ...
14
+ }
15
+ {
16
+ recursive_list_access creates a hash called `list` that is stored on the threadptr_recursive_hash. This is reported as a memory leak.
17
+ Memcheck:Leak
18
+ ...
19
+ fun:rb_ident_hash_new
20
+ fun:recursive_list_access
21
+ fun:exec_recursive
22
+ ...
23
+ }
24
+ {
25
+ "Invalid read of size 8" when marking the stack of fibers
26
+ Memcheck:Addr8
27
+ fun:each_location*
28
+ ...
29
+ }
30
+ {
31
+ Rust probes for statx(buf), will be fixed in Valgrind >= 3.1.6.0
32
+ Memcheck:Param
33
+ statx(buf)
34
+ ...
35
+ fun:*try_statx*
36
+ ...
37
+ }
38
+ {
39
+ Rust probes for statx(file_name), will be fixed in Valgrind >= 3.1.6.0
40
+ Memcheck:Param
41
+ statx(file_name)
42
+ ...
43
+ fun:*try_statx*
44
+ ...
45
+ }
46
+ {
47
+ strscan_do_scan in strscan.c will sometimes replace the ptr of the regex, which can be reported as a memory leak if the regex is stored in an iseq. https://github.com/ruby/ruby/pull/8136
48
+ Memcheck:Leak
49
+ ...
50
+ fun:rb_reg_prepare_re
51
+ fun:strscan_do_scan
52
+ ...
53
+ }
54
+ {
55
+ The callcache table (RCLASS_CC_TBL) is lazily created, so it is allocated when the first method that gets cached. If this happens in a native extension, it may be reported as a memory leak.
56
+ Memcheck:Leak
57
+ ...
58
+ fun:rb_id_table_create
59
+ ...
60
+ fun:rb_callable_method_entry
61
+ ...
62
+ }
63
+ {
64
+ The date library lazily initializes Regexps using static local variables through the function `regcomp`. The Regexp will end up being reported as a memory leak.
65
+ Memcheck:Leak
66
+ ...
67
+ fun:rb_enc_reg_new
68
+ ...
69
+ fun:date__parse
70
+ ...
71
+ }
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,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