uringmachine 0.20.0 → 0.22.0

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -4
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +34 -0
  5. data/TODO.md +132 -26
  6. data/benchmark/README.md +173 -0
  7. data/benchmark/bm_io_pipe.rb +70 -0
  8. data/benchmark/bm_io_socketpair.rb +71 -0
  9. data/benchmark/bm_mutex_cpu.rb +57 -0
  10. data/benchmark/bm_mutex_io.rb +64 -0
  11. data/benchmark/bm_pg_client.rb +109 -0
  12. data/benchmark/bm_queue.rb +76 -0
  13. data/benchmark/chart.png +0 -0
  14. data/benchmark/common.rb +135 -0
  15. data/benchmark/dns_client.rb +47 -0
  16. data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
  17. data/benchmark/run_bm.rb +8 -0
  18. data/benchmark/sqlite.rb +108 -0
  19. data/{examples/bm_write.rb → benchmark/write.rb} +6 -3
  20. data/ext/um/extconf.rb +1 -1
  21. data/ext/um/um.c +404 -95
  22. data/ext/um/um.h +77 -24
  23. data/ext/um/um_async_op.c +2 -2
  24. data/ext/um/um_class.c +168 -18
  25. data/ext/um/um_op.c +43 -0
  26. data/ext/um/um_sync.c +10 -16
  27. data/ext/um/um_utils.c +16 -0
  28. data/grant-2025/journal.md +242 -1
  29. data/grant-2025/tasks.md +136 -41
  30. data/lib/uringmachine/actor.rb +8 -0
  31. data/lib/uringmachine/dns_resolver.rb +1 -2
  32. data/lib/uringmachine/fiber_scheduler.rb +283 -110
  33. data/lib/uringmachine/version.rb +1 -1
  34. data/lib/uringmachine.rb +32 -3
  35. data/test/helper.rb +7 -18
  36. data/test/test_actor.rb +12 -3
  37. data/test/test_async_op.rb +10 -10
  38. data/test/test_fiber.rb +84 -1
  39. data/test/test_fiber_scheduler.rb +1425 -20
  40. data/test/test_um.rb +565 -113
  41. data/uringmachine.gemspec +6 -5
  42. data/vendor/liburing/src/include/liburing/io_uring.h +1 -0
  43. data/vendor/liburing/src/include/liburing.h +13 -0
  44. data/vendor/liburing/src/liburing-ffi.map +1 -0
  45. data/vendor/liburing/test/bind-listen.c +175 -13
  46. data/vendor/liburing/test/read-write.c +4 -4
  47. data/vendor/liburing/test/ringbuf-read.c +4 -4
  48. data/vendor/liburing/test/send_recv.c +8 -7
  49. metadata +50 -28
  50. data/examples/bm_fileno.rb +0 -33
  51. data/examples/bm_queue.rb +0 -110
  52. data/examples/bm_side_running.rb +0 -83
  53. data/examples/bm_sqlite.rb +0 -89
  54. data/examples/dns_client.rb +0 -12
  55. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  56. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  57. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  58. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
@@ -2,17 +2,46 @@
2
2
 
3
3
  require_relative 'helper'
4
4
  require 'uringmachine/fiber_scheduler'
5
+ require 'securerandom'
6
+ require 'socket'
7
+ require 'net/http'
8
+ require 'json'
9
+
10
+ class MethodCallAuditor
11
+ attr_reader :calls
12
+
13
+ def initialize(target)
14
+ @target = target
15
+ @calls = []
16
+ end
17
+
18
+ def respond_to?(sym, include_all = false) = @target.respond_to?(sym, include_all)
19
+
20
+ def method_missing(sym, *args, &block)
21
+ res = @target.send(sym, *args, &block)
22
+ @calls << ({ sym:, args:, res:})
23
+ res
24
+ rescue Exception => e
25
+ @calls << ({ sym:, args:, res: e})
26
+ raise
27
+ end
28
+
29
+ def last_call
30
+ calls.last
31
+ end
32
+ end
5
33
 
6
34
  class FiberSchedulerTest < UMBaseTest
7
35
  def setup
8
36
  super
9
- @scheduler = UM::FiberScheduler.new(@machine)
37
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
38
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
10
39
  Fiber.set_scheduler(@scheduler)
11
40
  end
12
41
 
13
42
  def teardown
14
43
  Fiber.set_scheduler(nil)
15
- GC.start
44
+ super
16
45
  end
17
46
 
18
47
  def test_fiber_scheduler_initialize_without_machine
@@ -20,16 +49,6 @@ class FiberSchedulerTest < UMBaseTest
20
49
  assert_kind_of UringMachine, s.machine
21
50
  end
22
51
 
23
- def test_fiber_scheduler_post_fork
24
- Fiber.schedule {}
25
- assert_equal 1, @scheduler.fiber_map.size
26
-
27
- machine_before = @scheduler.machine
28
- @scheduler.post_fork
29
- refute_equal machine_before, @scheduler.machine
30
- assert_equal 0, @scheduler.fiber_map.size
31
- end
32
-
33
52
  def test_fiber_scheduler_spinning
34
53
  f1 = Fiber.schedule do
35
54
  sleep 0.001
@@ -41,26 +60,30 @@ class FiberSchedulerTest < UMBaseTest
41
60
 
42
61
  assert_kind_of Fiber, f1
43
62
  assert_kind_of Fiber, f2
63
+
64
+ assert_equal 2, @scheduler.calls.size
65
+ assert_equal [:fiber] * 2, @scheduler.calls.map { it[:sym] }
44
66
  assert_equal 2, @scheduler.fiber_map.size
45
67
 
46
68
  # close scheduler
47
69
  Fiber.set_scheduler nil
70
+ assert_equal :scheduler_close, @scheduler.last_call[:sym]
48
71
  GC.start
49
72
  assert_equal 0, @scheduler.fiber_map.size
50
73
  end
51
74
 
52
- def test_fiber_scheduler_basic_io
75
+ def test_fiber_scheduler_io_read_io_write
53
76
  i, o = IO.pipe
54
77
  buffer = []
55
78
 
56
79
  f1 = Fiber.schedule do
57
- sleep 0.001
80
+ sleep 0.01
58
81
  o.write 'foo'
59
82
  buffer << :f1
60
83
  end
61
84
 
62
85
  f2 = Fiber.schedule do
63
- sleep 0.002
86
+ sleep 0.02
64
87
  o.write 'bar'
65
88
  buffer << :f2
66
89
  o.close
@@ -74,27 +97,163 @@ class FiberSchedulerTest < UMBaseTest
74
97
  @scheduler.join
75
98
  assert_equal [true] * 3, [f1, f2, f3].map(&:done?)
76
99
  assert_equal [:f1, :f2, 'foobar'], buffer
100
+
101
+ assert_equal({
102
+ fiber: 3,
103
+ kernel_sleep: 2,
104
+ io_write: 2,
105
+ io_read: 3,
106
+ io_close: 1,
107
+ join: 1
108
+ }, @scheduler.calls.map { it[:sym] }.tally)
109
+ ensure
110
+ i.close rescue nil
111
+ o.close rescue nil
112
+ end
113
+
114
+ def test_io_read_with_timeout
115
+ i, o = IO.pipe
116
+ i.timeout = 0.01
117
+ buf = []
118
+
119
+ Fiber.schedule do
120
+ buf << i.read
121
+ rescue Timeout::Error
122
+ buf << :timeout
123
+ end
124
+ @scheduler.join
125
+ assert_equal [:timeout], buf
126
+
127
+ assert_equal({
128
+ fiber: 1,
129
+ io_read: 1,
130
+ join: 1
131
+ }, @scheduler.calls.map { it[:sym] }.tally)
132
+ ensure
133
+ i.close rescue nil
134
+ o.close rescue nil
135
+ end
136
+
137
+ def test_io_write_with_timeout
138
+ i, o = IO.pipe
139
+ o << ('*' * (1 << 16))
140
+ o.timeout = 0.01
141
+
142
+ buf = []
143
+
144
+ Fiber.schedule do
145
+ buf << o.write('!')
146
+ rescue Timeout::Error
147
+ buf << :timeout
148
+ end
149
+ @scheduler.join
150
+ assert_equal [:timeout], buf
151
+
152
+ assert_equal({
153
+ fiber: 1,
154
+ io_write: 1,
155
+ join: 1
156
+ }, @scheduler.calls.map { it[:sym] }.tally)
77
157
  ensure
78
158
  i.close rescue nil
79
159
  o.close rescue nil
80
160
  end
81
161
 
162
+ def test_io_write_ioerror
163
+ i, o = IO.pipe
164
+ buf = []
165
+
166
+ Fiber.schedule do
167
+ buf << i.write('!')
168
+ rescue SystemCallError, IOError => e
169
+ buf << e
170
+ end
171
+ @scheduler.join
172
+ assert_kind_of IOError, buf.first
173
+
174
+ assert_equal({
175
+ fiber: 1,
176
+ join: 1
177
+ }, @scheduler.calls.map { it[:sym] }.tally)
178
+ ensure
179
+ i.close rescue nil
180
+ o.close rescue nil
181
+ end
182
+
183
+ def test_fiber_io_pread
184
+ fn = "/tmp/um_#{SecureRandom.hex}"
185
+ IO.write(fn, 'foobar')
186
+
187
+ buf = nil
188
+ Fiber.schedule do
189
+ File.open(fn, 'r') do |f|
190
+ buf = f.pread(3, 2)
191
+ end
192
+ rescue => e
193
+ buf = e
194
+ end
195
+
196
+ @scheduler.join
197
+ assert_equal 'oba', buf
198
+ assert_equal({
199
+ fiber: 1,
200
+ blocking_operation_wait: 1,
201
+ io_pread: 1,
202
+ io_close: 1,
203
+ join: 1
204
+ }, @scheduler.calls.map { it[:sym] }.tally)
205
+ ensure
206
+ FileUtils.rm(fn) rescue nil
207
+ end
208
+
209
+ def test_fiber_scheduler_io_pwrite
210
+ fn = "/tmp/um_#{SecureRandom.hex}"
211
+ IO.write(fn, 'foobar')
212
+
213
+ res = nil
214
+ Fiber.schedule do
215
+ File.open(fn, 'r+') do |f|
216
+ res = f.pwrite('baz', 2)
217
+ end
218
+ end
219
+
220
+ @scheduler.join
221
+ assert_equal 3, res
222
+
223
+ assert_equal 'fobazr', IO.read(fn)
224
+ assert_equal({
225
+ fiber: 1,
226
+ blocking_operation_wait: 1,
227
+ io_pwrite: 1,
228
+ io_close: 1,
229
+ join: 1
230
+ }, @scheduler.calls.map { it[:sym] }.tally)
231
+ ensure
232
+ FileUtils.rm(fn) rescue nil
233
+ end
234
+
82
235
  def test_fiber_scheduler_sleep
83
236
  t0 = monotonic_clock
84
- assert_equal 0, machine.pending_count
237
+ assert_equal 0, machine.metrics[:ops_pending]
85
238
  Fiber.schedule do
86
239
  sleep(0.01)
87
240
  end
88
241
  Fiber.schedule do
89
242
  sleep(0.02)
90
243
  end
91
- assert_equal 2, machine.pending_count
244
+ assert_equal 2, machine.metrics[:ops_pending]
92
245
  @scheduler.join
93
246
  t1 = monotonic_clock
94
247
  assert_in_range 0.02..0.025, t1 - t0
248
+
249
+ assert_equal({
250
+ fiber: 2,
251
+ kernel_sleep: 2,
252
+ join: 1
253
+ }, @scheduler.calls.map { it[:sym] }.tally)
95
254
  end
96
255
 
97
- def test_fiber_scheduler_lock
256
+ def test_fiber_scheduler_block
98
257
  mutex = Mutex.new
99
258
  buffer = []
100
259
  t0 = monotonic_clock
@@ -109,15 +268,25 @@ class FiberSchedulerTest < UMBaseTest
109
268
  end
110
269
  @scheduler.join
111
270
  t1 = monotonic_clock
112
- assert_in_range 0.01..0.015, t1 - t0
271
+ assert_in_range 0.01..0.020, t1 - t0
272
+ assert_equal({
273
+ fiber: 3,
274
+ kernel_sleep: 12,
275
+ block: 1,
276
+ unblock: 1,
277
+ join: 1
278
+ }, @scheduler.calls.map { it[:sym] }.tally)
113
279
  end
114
280
 
115
281
  def test_fiber_scheduler_process_wait
282
+ skip("Missing #process_wait hook (no rb_process_status_new)") \
283
+ if !@scheduler.respond_to?(:process_wait)
284
+
116
285
  child_pid = nil
117
286
  status = nil
118
287
  f1 = Fiber.schedule do
119
288
  child_pid = fork {
120
- Fiber.scheduler.post_fork
289
+ Fiber.scheduler.process_fork
121
290
  Fiber.set_scheduler nil
122
291
  sleep(0.01);
123
292
  exit! 42
@@ -130,9 +299,1245 @@ class FiberSchedulerTest < UMBaseTest
130
299
  assert_kind_of Process::Status, status
131
300
  assert_equal child_pid, status.pid
132
301
  assert_equal 42, status.exitstatus
302
+ assert_equal({
303
+ fiber: 1,
304
+ process_wait: 1,
305
+ join: 1
306
+ }, @scheduler.calls.map { it[:sym] }.tally)
133
307
  ensure
134
308
  if child_pid
135
309
  Process.wait(child_pid) rescue nil
136
310
  end
137
311
  end
312
+
313
+ # Currently the fiber scheduler doesn't have hooks for send/recv. The only
314
+ # hook that will be invoked is `io_wait`.
315
+ def test_fiber_scheduler_sockets
316
+ s1, s2 = UNIXSocket.pair(:STREAM)
317
+
318
+ buf = +''
319
+ sent = nil
320
+
321
+ assert_equal 0, machine.metrics[:total_ops]
322
+ Fiber.schedule do
323
+ buf = s1.recv(12)
324
+ end
325
+ Fiber.schedule do
326
+ sent = s2.send('foobar', 0)
327
+ end
328
+
329
+ # In Ruby, sockets are by default non-blocking. The recv will cause io_wait
330
+ # to be invoked, the send should get through without needing to poll.
331
+ assert_equal 1, machine.metrics[:total_ops]
332
+ @scheduler.join
333
+
334
+ assert_equal 6, sent
335
+ assert_equal 'foobar', buf
336
+ assert_equal({
337
+ fiber: 2,
338
+ io_wait: 1,
339
+ join: 1
340
+ }, @scheduler.calls.map { it[:sym] }.tally)
341
+ ensure
342
+ s1.close rescue nil
343
+ s2.close rescue nil
344
+ end
345
+
346
+ def test_fiber_scheduler_io_write_io_read
347
+ fn = "/tmp/um_#{SecureRandom.hex}"
348
+ Fiber.schedule do
349
+ IO.write(fn, 'foobar')
350
+ end
351
+ assert_equal 1, machine.metrics[:total_ops]
352
+
353
+ buf = nil
354
+ Fiber.schedule do
355
+ sleep 0.001
356
+ buf = IO.read(fn)
357
+ end
358
+ assert_equal 2, machine.metrics[:total_ops]
359
+
360
+ @scheduler.join
361
+ assert_equal 'foobar', buf
362
+ assert_equal({
363
+ fiber: 2,
364
+ blocking_operation_wait: 2,
365
+ io_read: 2,
366
+ io_close: 2,
367
+ kernel_sleep: 1,
368
+ join: 1
369
+ }, @scheduler.calls.map { it[:sym] }.tally)
370
+ ensure
371
+ FileUtils.rm(fn) rescue nil
372
+ end
373
+
374
+ def test_fiber_scheduler_file_io
375
+ fn = "/tmp/um_#{SecureRandom.hex}"
376
+ Fiber.schedule do
377
+ File.open(fn, 'w') { it.write 'foobar' }
378
+ end
379
+ assert_equal 1, machine.metrics[:total_ops]
380
+
381
+ buf = nil
382
+ Fiber.schedule do
383
+ sleep 0.001
384
+ File.open(fn, 'r') { buf = it.read }
385
+ end
386
+ assert_equal 2, machine.metrics[:total_ops]
387
+ @scheduler.join
388
+ assert_equal 'foobar', buf
389
+ assert_equal({
390
+ fiber: 2,
391
+ blocking_operation_wait: 2,
392
+ io_read: 2,
393
+ io_close: 2,
394
+ kernel_sleep: 1,
395
+ join: 1
396
+ }, @scheduler.calls.map { it[:sym] }.tally)
397
+ ensure
398
+ FileUtils.rm(fn) rescue nil
399
+ end
400
+
401
+ def test_fiber_scheduler_mutex
402
+ mutex = Mutex.new
403
+
404
+ buf = []
405
+ Fiber.schedule do
406
+ buf << 11
407
+ mutex.synchronize {
408
+ buf << [12, machine.metrics[:total_ops]]
409
+ sleep 0.01
410
+ buf << [13, machine.metrics[:total_ops]]
411
+ }
412
+ buf << 14
413
+ end
414
+ assert_equal 1, machine.metrics[:total_ops]
415
+
416
+ Fiber.schedule do
417
+ buf << 21
418
+ mutex.synchronize {
419
+ buf << [22, machine.metrics[:total_ops]]
420
+ sleep 0.01
421
+ buf << [23, machine.metrics[:total_ops]]
422
+ }
423
+ buf << 24
424
+ end
425
+ assert_equal 1, machine.metrics[:total_ops]
426
+
427
+ @scheduler.join
428
+ assert_equal [11, [12, 0], 21, [13, 2], 14, [22, 2], [23, 4], 24], buf
429
+ assert_equal({
430
+ fiber: 2,
431
+ kernel_sleep: 2,
432
+ block: 1,
433
+ unblock: 1,
434
+ join: 1
435
+ }, @scheduler.calls.map { it[:sym] }.tally)
436
+ end
437
+
438
+ def test_fiber_scheduler_queue_shift
439
+ queue = Queue.new
440
+
441
+ buf = []
442
+ Fiber.schedule do
443
+ buf << [11, machine.metrics[:total_ops]]
444
+ buf << queue.shift
445
+ buf << [12, machine.metrics[:total_ops]]
446
+ end
447
+ Fiber.schedule do
448
+ buf << [21, machine.metrics[:total_ops]]
449
+ queue << :foo
450
+ buf << [22, machine.metrics[:total_ops]]
451
+ end
452
+ assert_equal 0, machine.metrics[:total_ops]
453
+ @scheduler.join
454
+
455
+ assert_equal [[11, 0], [21, 0], [22, 0], :foo, [12, 1]], buf
456
+ assert_equal({
457
+ fiber: 2,
458
+ block: 1,
459
+ unblock: 1,
460
+ join: 1
461
+ }, @scheduler.calls.map { it[:sym] }.tally)
462
+ end
463
+
464
+ def test_fiber_scheduler_queue_shift_with_timeout
465
+ queue = Queue.new
466
+
467
+ buf = []
468
+ Fiber.schedule do
469
+ buf << [11, machine.metrics[:total_ops]]
470
+ buf << queue.shift(timeout: 0.01)
471
+ buf << [12, machine.metrics[:total_ops]]
472
+ end
473
+ Fiber.schedule do
474
+ buf << [21, machine.metrics[:total_ops]]
475
+ end
476
+ assert_equal 1, machine.metrics[:total_ops]
477
+ @scheduler.join
478
+
479
+ assert_equal [[11, 0], [21, 1], nil, [12, 2]], buf
480
+ assert_equal({
481
+ fiber: 2,
482
+ block: 1,
483
+ join: 1
484
+ }, @scheduler.calls.map { it[:sym] }.tally)
485
+ end
486
+
487
+ def test_fiber_scheduler_thread_join
488
+ thread = Thread.new do
489
+ sleep 0.1
490
+ end
491
+ Fiber.schedule do
492
+ thread.join
493
+ end
494
+
495
+ # No ops are issued, except for a NOP SQE used to wakeup the waiting thread.
496
+ assert_equal 0, machine.metrics[:total_ops]
497
+
498
+ @scheduler.join
499
+ assert_equal 1, machine.metrics[:total_ops]
500
+ assert_equal({
501
+ fiber: 1,
502
+ block: 1,
503
+ unblock: 1,
504
+ join: 1
505
+ }, @scheduler.calls.map { it[:sym] }.tally)
506
+ end
507
+
508
+ def test_fiber_scheduler_system
509
+ skip("Missing #process_wait hook (no rb_process_status_new)") \
510
+ if !@scheduler.respond_to?(:process_wait)
511
+
512
+ buf = []
513
+ Fiber.schedule do
514
+ buf << system('sleep 0.01')
515
+ end
516
+ @scheduler.join
517
+ assert_equal [true], buf
518
+ assert_equal({
519
+ fiber: 1,
520
+ process_wait: 1,
521
+ join: 1
522
+ }, @scheduler.calls.map { it[:sym] }.tally)
523
+ ensure
524
+ Process.wait(0, Process::WNOHANG) rescue nil
525
+ end
526
+
527
+ def test_fiber_scheduler_cmd
528
+ skip("Missing #process_wait hook (no rb_process_status_new)") \
529
+ if !@scheduler.respond_to?(:process_wait)
530
+
531
+ buf = []
532
+ Fiber.schedule do
533
+ buf << `echo 'foo'`
534
+ end
535
+ assert_equal 1, machine.metrics[:total_ops]
536
+ @scheduler.join
537
+ assert_equal ["foo\n"], buf
538
+ assert_equal({
539
+ fiber: 1,
540
+ io_read: 2,
541
+ process_wait: 1,
542
+ join: 1
543
+ }, @scheduler.calls.map { it[:sym] }.tally)
544
+ ensure
545
+ Process.wait(0, Process::WNOHANG) rescue nil
546
+ end
547
+
548
+ def test_fiber_scheduler_popen
549
+ skip("Missing #process_wait hook (no rb_process_status_new)") \
550
+ if !@scheduler.respond_to?(:process_wait)
551
+
552
+ buf = []
553
+ Fiber.schedule do
554
+ IO.popen('ruby', 'r+') do |pipe|
555
+ buf << [11, machine.metrics[:total_ops]]
556
+ pipe.puts 'puts "bar"'
557
+ buf << [12, machine.metrics[:total_ops]]
558
+ pipe.close_write
559
+ buf << [13, pipe.gets.chomp, machine.metrics[:total_ops]]
560
+ end
561
+ end
562
+ assert_equal 1, machine.metrics[:total_ops]
563
+ @scheduler.join
564
+ assert_equal [[11, 0], [12, 3], [13, "bar", 5]], buf
565
+ assert_equal({
566
+ fiber: 1,
567
+ io_write: 2,
568
+ io_read: 1,
569
+ blocking_operation_wait: 1,
570
+ process_wait: 1,
571
+ join: 1
572
+ }, @scheduler.calls.map { it[:sym] }.tally)
573
+ ensure
574
+ Process.wait(0, Process::WNOHANG) rescue nil
575
+ end
576
+
577
+ def test_fiber_scheduler_fiber_interrupt
578
+ r, w = IO.pipe
579
+ w << 'foo'
580
+
581
+ exception = nil
582
+ Fiber.schedule do
583
+ r.read
584
+ rescue Exception => e
585
+ exception = e
586
+ end
587
+ assert_equal 1, machine.metrics[:total_ops]
588
+ machine.snooze
589
+ Thread.new {
590
+ r.close
591
+ }
592
+ @scheduler.join
593
+ assert_kind_of IOError, exception
594
+ assert_equal({
595
+ fiber: 1,
596
+ io_read: 2,
597
+ fiber_interrupt: 1,
598
+ join: 1
599
+ }, @scheduler.calls.map { it[:sym] }.tally)
600
+ ensure
601
+ r.close rescue nil
602
+ w.close rescue nil
603
+ end
604
+
605
+ def test_fiber_scheduler_address_resolve
606
+ addrs = nil
607
+ Fiber.schedule do
608
+ addrs = Addrinfo.getaddrinfo("localhost", 80, Socket::AF_INET, :STREAM)
609
+ end
610
+ assert_equal 1, machine.metrics[:total_ops]
611
+ @scheduler.join
612
+ assert_kind_of Array, addrs
613
+ addr = addrs.first
614
+ assert_kind_of Addrinfo, addr
615
+ assert_includes ['127.0.0.1', '::1'], addr.ip_address
616
+ assert_equal({
617
+ fiber: 1,
618
+ io_read: 2,
619
+ io_close: 1,
620
+ blocking_operation_wait: 1,
621
+ address_resolve: 1,
622
+ join: 1
623
+ }, @scheduler.calls.map { it[:sym] }.tally)
624
+ end
625
+
626
+ def test_fiber_scheduler_timeout_after
627
+ res = nil
628
+ Fiber.schedule do
629
+ Timeout.timeout(0.05) do
630
+ sleep 1
631
+ end
632
+ res = true
633
+ rescue => e
634
+ res = e
635
+ end
636
+ @scheduler.join
637
+ assert_equal 3, machine.metrics[:total_ops]
638
+ assert_kind_of Timeout::Error, res
639
+ assert_equal({
640
+ fiber: 1,
641
+ timeout_after: 1,
642
+ kernel_sleep: 1,
643
+ join: 1
644
+ }, @scheduler.calls.map { it[:sym] }.tally)
645
+ end
646
+
647
+ def test_fiber_scheduler_io_select
648
+ r, w = IO.pipe
649
+ buf = []
650
+
651
+ Fiber.schedule do
652
+ buf << IO.select([r], [], [])
653
+ buf << IO.select([], [w], [])
654
+ end
655
+ @machine.snooze
656
+ w << 'foo'
657
+ @machine.snooze
658
+ assert_equal [[[r], [], []]], buf
659
+ @machine.snooze
660
+ @scheduler.join
661
+ assert_equal [[[r], [], []], [[], [w], []]], buf
662
+ ensure
663
+ r.close rescue nil
664
+ w.close rescue nil
665
+ end
666
+
667
+ def test_fiber_scheduler_blocking_operation_wait_single_issuer
668
+ buf = []
669
+ (1..10).each { |i|
670
+ op = -> { i * 10}
671
+ buf << @scheduler.blocking_operation_wait(op)
672
+ sleep 0.01
673
+ @machine.snooze
674
+ }
675
+ assert_equal (1..10).map { it * 10 }, buf
676
+
677
+ buf = []
678
+ (1..20).each { |i|
679
+ op = -> { i * 10}
680
+ Fiber.schedule do
681
+ sleep 0.001
682
+ buf << @scheduler.blocking_operation_wait(op)
683
+ sleep 0.001
684
+ end
685
+ }
686
+ @scheduler.join
687
+
688
+ assert_equal (1..20).map { it * 10 }, buf.sort
689
+ end
690
+ end
691
+
692
+ class FiberSchedulerIOClassMethodsTest < UMBaseTest
693
+ def setup
694
+ super
695
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
696
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
697
+ Fiber.set_scheduler(@scheduler)
698
+ @fn = "/tmp/um_#{SecureRandom.hex}"
699
+ IO.write(@fn, '===')
700
+ end
701
+
702
+ def teardown
703
+ FileUtils.rm(@fn) rescue nil
704
+ Fiber.set_scheduler(nil)
705
+ super
706
+ end
707
+
708
+ def test_IO_s_binread
709
+ ret = nil
710
+ Fiber.schedule do
711
+ ret = IO.binread(@fn)
712
+ end
713
+ @scheduler.join
714
+ assert_equal '===', ret
715
+ assert_equal({
716
+ fiber: 1,
717
+ io_read: 2,
718
+ blocking_operation_wait: 1,
719
+ io_close: 1,
720
+ join: 1
721
+ }, @scheduler.calls.map { it[:sym] }.tally)
722
+ end
723
+
724
+ def test_IO_s_binwrite
725
+ ret = nil
726
+ Fiber.schedule do
727
+ ret = IO.binwrite(@fn, '***', 2)
728
+ end
729
+ @scheduler.join
730
+ assert_equal 3, ret
731
+ assert_equal '==***', IO.read(@fn)
732
+ assert_equal({
733
+ fiber: 1,
734
+ blocking_operation_wait: 1,
735
+ io_close: 1,
736
+ join: 1
737
+ }, @scheduler.calls.map { it[:sym] }.tally)
738
+ end
739
+
740
+ def test_IO_s_copy_stream
741
+ fn2 = "/tmp/um_#{SecureRandom.hex}"
742
+ ret = nil
743
+ Fiber.schedule do
744
+ ret = IO.copy_stream(@fn, fn2)
745
+ end
746
+ @scheduler.join
747
+ assert_equal 3, ret
748
+ assert_equal '===', IO.read(fn2)
749
+ assert_equal({
750
+ fiber: 1,
751
+ blocking_operation_wait: 3,
752
+ io_close: 2,
753
+ join: 1
754
+ }, @scheduler.calls.map { it[:sym] }.tally)
755
+ ensure
756
+ FileUtils.rm(fn2) rescue nil
757
+ end
758
+
759
+ def test_IO_s_foreach
760
+ buf = []
761
+ Fiber.schedule do
762
+ IO.foreach(@fn) { buf << it }
763
+ end
764
+ @scheduler.join
765
+ assert_equal ['==='], buf
766
+ assert_equal({
767
+ fiber: 1,
768
+ io_read: 3,
769
+ blocking_operation_wait: 1,
770
+ io_close: 1,
771
+ join: 1
772
+ }, @scheduler.calls.map { it[:sym] }.tally)
773
+ end
774
+
775
+ def test_IO_s_foreach
776
+ buf = []
777
+ Fiber.schedule do
778
+ IO.foreach(@fn) { buf << it }
779
+ end
780
+ @scheduler.join
781
+ assert_equal ['==='], buf
782
+ assert_equal({
783
+ fiber: 1,
784
+ io_read: 3,
785
+ blocking_operation_wait: 1,
786
+ io_close: 1,
787
+ join: 1
788
+ }, @scheduler.calls.map { it[:sym] }.tally)
789
+ end
790
+
791
+ def test_IO_s_popen
792
+ ret = nil
793
+ Fiber.schedule do
794
+ IO.popen("cat #{@fn}") { ret = it.read }
795
+ end
796
+ @scheduler.join
797
+ assert_equal '===', ret
798
+ assert_equal({
799
+ fiber: 1,
800
+ io_read: 2,
801
+ io_close: 1,
802
+ join: 1
803
+ }, @scheduler.calls.map { it[:sym] }.tally)
804
+ end
805
+
806
+ def test_IO_s_read
807
+ ret = nil
808
+ Fiber.schedule do
809
+ ret = IO.read(@fn)
810
+ end
811
+ @scheduler.join
812
+ assert_equal '===', ret
813
+ assert_equal({
814
+ fiber: 1,
815
+ io_read: 2,
816
+ blocking_operation_wait: 1,
817
+ io_close: 1,
818
+ join: 1
819
+ }, @scheduler.calls.map { it[:sym] }.tally)
820
+ end
821
+
822
+ def test_IO_s_readlines
823
+ ret = nil
824
+ Fiber.schedule do
825
+ ret = IO.readlines(@fn)
826
+ end
827
+ @scheduler.join
828
+ assert_equal ['==='], ret
829
+ assert_equal({
830
+ fiber: 1,
831
+ io_read: 3,
832
+ blocking_operation_wait: 1,
833
+ io_close: 1,
834
+ join: 1
835
+ }, @scheduler.calls.map { it[:sym] }.tally)
836
+ end
837
+
838
+ def test_IO_s_select
839
+ ret = nil
840
+ io = File.open(@fn, 'r+')
841
+ Fiber.schedule do
842
+ ret = IO.select([io], [io], [])
843
+ end
844
+ @scheduler.join
845
+ assert_equal [[io], [io], []], ret
846
+ assert_equal({
847
+ fiber: 1,
848
+ io_select: 1,
849
+ join: 1
850
+ }, @scheduler.calls.map { it[:sym] }.tally)
851
+ ensure
852
+ io.close rescue nil
853
+ end
854
+
855
+ def test_IO_s_write
856
+ ret = nil
857
+ Fiber.schedule do
858
+ ret = IO.write(@fn, '***', 2)
859
+ end
860
+ @scheduler.join
861
+ assert_equal 3, ret
862
+ assert_equal '==***', IO.read(@fn)
863
+ assert_equal({
864
+ fiber: 1,
865
+ blocking_operation_wait: 1,
866
+ io_close: 1,
867
+ join: 1
868
+ }, @scheduler.calls.map { it[:sym] }.tally)
869
+ end
870
+ end
871
+
872
+ class FiberSchedulerIOInstanceMethodsTest < UMBaseTest
873
+ def setup
874
+ super
875
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
876
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
877
+ Fiber.set_scheduler(@scheduler)
878
+ @fn = "/tmp/um_#{SecureRandom.hex}"
879
+ IO.write(@fn, '===')
880
+ @io = File.open(@fn, 'r+')
881
+ @io.sync = true
882
+ end
883
+
884
+ def teardown
885
+ @io.close rescue nil
886
+ FileUtils.rm(@fn) rescue nil
887
+ Fiber.set_scheduler(nil)
888
+ super
889
+ end
890
+
891
+ def test_IO_i_double_left_chevron
892
+ Fiber.schedule do
893
+ @io.seek(2)
894
+ @io << '***'
895
+ end
896
+ @scheduler.join
897
+ assert_equal '==***', IO.read(@fn)
898
+ assert_equal({
899
+ fiber: 1,
900
+ io_write: 1,
901
+ join: 1
902
+ }, @scheduler.calls.map { it[:sym] }.tally)
903
+ end
904
+
905
+ def test_IO_i_close
906
+ ret = nil
907
+ Fiber.schedule do
908
+ ret = @io.close
909
+ end
910
+ @scheduler.join
911
+ assert_nil ret
912
+ assert_equal({
913
+ fiber: 1,
914
+ io_close: 1,
915
+ join: 1
916
+ }, @scheduler.calls.map { it[:sym] }.tally)
917
+ end
918
+
919
+ def test_IO_i_close_read
920
+ ret = nil
921
+ Fiber.schedule do
922
+ ret = @io.close_read
923
+ end
924
+ @scheduler.join
925
+ assert_nil ret
926
+ assert_equal({
927
+ fiber: 1,
928
+ join: 1
929
+ }, @scheduler.calls.map { it[:sym] }.tally)
930
+ end
931
+
932
+ def test_IO_i_close_write
933
+ ret = nil
934
+ Fiber.schedule do
935
+ ret = @io.close_write
936
+ end
937
+ @scheduler.join
938
+ assert_nil ret
939
+ assert_equal({
940
+ fiber: 1,
941
+ join: 1
942
+ }, @scheduler.calls.map { it[:sym] }.tally)
943
+ end
944
+
945
+ def test_IO_i_each_byte
946
+ buf = []
947
+ Fiber.schedule do
948
+ @io.each_byte { buf << it }
949
+ end
950
+ @scheduler.join
951
+ assert_equal [61, 61, 61], buf
952
+ assert_equal({
953
+ fiber: 1,
954
+ io_read: 2,
955
+ join: 1
956
+ }, @scheduler.calls.map { it[:sym] }.tally)
957
+ end
958
+
959
+ def test_IO_i_each_char
960
+ buf = []
961
+ Fiber.schedule do
962
+ @io.each_char { buf << it }
963
+ end
964
+ @scheduler.join
965
+ assert_equal ['=', '=', '='], buf
966
+ assert_equal({
967
+ fiber: 1,
968
+ io_read: 2,
969
+ join: 1
970
+ }, @scheduler.calls.map { it[:sym] }.tally)
971
+ end
972
+
973
+ def test_IO_i_each_line
974
+ buf = []
975
+ Fiber.schedule do
976
+ @io.each_line { buf << it }
977
+ end
978
+ @scheduler.join
979
+ assert_equal ['==='], buf
980
+ assert_equal({
981
+ fiber: 1,
982
+ io_read: 3,
983
+ join: 1
984
+ }, @scheduler.calls.map { it[:sym] }.tally)
985
+ end
986
+
987
+ def test_IO_i_getbyte
988
+ ret = nil
989
+ Fiber.schedule do
990
+ ret = @io.getbyte
991
+ end
992
+ @scheduler.join
993
+ assert_equal 61, ret
994
+ assert_equal({
995
+ fiber: 1,
996
+ io_read: 1,
997
+ join: 1
998
+ }, @scheduler.calls.map { it[:sym] }.tally)
999
+ end
1000
+
1001
+ def test_IO_i_getc
1002
+ ret = nil
1003
+ Fiber.schedule do
1004
+ ret = @io.getc
1005
+ end
1006
+ @scheduler.join
1007
+ assert_equal '=', ret
1008
+ assert_equal({
1009
+ fiber: 1,
1010
+ io_read: 1,
1011
+ join: 1
1012
+ }, @scheduler.calls.map { it[:sym] }.tally)
1013
+ end
1014
+
1015
+ def test_IO_i_gets
1016
+ ret = nil
1017
+ Fiber.schedule do
1018
+ ret = @io.gets
1019
+ end
1020
+ @scheduler.join
1021
+ assert_equal '===', ret
1022
+ assert_equal({
1023
+ fiber: 1,
1024
+ io_read: 2,
1025
+ join: 1
1026
+ }, @scheduler.calls.map { it[:sym] }.tally)
1027
+ end
1028
+
1029
+ def test_IO_i_pread
1030
+ ret = nil
1031
+ Fiber.schedule do
1032
+ ret = @io.pread(5, 2)
1033
+ end
1034
+ @scheduler.join
1035
+ assert_equal '=', ret
1036
+ assert_equal({
1037
+ fiber: 1,
1038
+ io_pread: 1,
1039
+ join: 1
1040
+ }, @scheduler.calls.map { it[:sym] }.tally)
1041
+ end
1042
+
1043
+ def test_IO_i_print
1044
+ ret = nil
1045
+ Fiber.schedule do
1046
+ ret = @io.print(1, 2.0, '3')
1047
+ end
1048
+ @scheduler.join
1049
+ assert_nil ret
1050
+ assert_equal '12.03', IO.read(@fn)
1051
+ assert_equal({
1052
+ fiber: 1,
1053
+ io_write: 3,
1054
+ join: 1
1055
+ }, @scheduler.calls.map { it[:sym] }.tally)
1056
+ end
1057
+
1058
+ def test_IO_i_printf
1059
+ ret = nil
1060
+ Fiber.schedule do
1061
+ ret = @io.printf('%08x', 4321)
1062
+ end
1063
+ @scheduler.join
1064
+ assert_nil ret
1065
+ assert_equal '000010e1', IO.read(@fn)
1066
+ assert_equal({
1067
+ fiber: 1,
1068
+ io_write: 1,
1069
+ join: 1
1070
+ }, @scheduler.calls.map { it[:sym] }.tally)
1071
+ end
1072
+
1073
+ def test_IO_i_putc
1074
+ ret = nil
1075
+ Fiber.schedule do
1076
+ ret = @io.putc('B')
1077
+ end
1078
+ @scheduler.join
1079
+ assert_equal 'B', ret
1080
+ assert_equal 'B==', IO.read(@fn)
1081
+ assert_equal({
1082
+ fiber: 1,
1083
+ io_write: 1,
1084
+ join: 1
1085
+ }, @scheduler.calls.map { it[:sym] }.tally)
1086
+ end
1087
+
1088
+ def test_IO_i_puts
1089
+ ret = nil
1090
+ Fiber.schedule do
1091
+ ret = @io.puts('abc')
1092
+ end
1093
+ @scheduler.join
1094
+ assert_nil ret
1095
+ assert_equal "abc\n", IO.read(@fn)
1096
+ assert_equal({
1097
+ fiber: 1,
1098
+ io_write: 2,
1099
+ join: 1
1100
+ }, @scheduler.calls.map { it[:sym] }.tally)
1101
+ end
1102
+
1103
+ def test_IO_i_pwrite
1104
+ ret = nil
1105
+ Fiber.schedule do
1106
+ ret = @io.pwrite('abc', 2)
1107
+ end
1108
+ @scheduler.join
1109
+ assert_equal 3, ret
1110
+ assert_equal "==abc", IO.read(@fn)
1111
+ assert_equal({
1112
+ fiber: 1,
1113
+ io_pwrite: 1,
1114
+ join: 1
1115
+ }, @scheduler.calls.map { it[:sym] }.tally)
1116
+ end
1117
+
1118
+ def test_IO_i_read
1119
+ ret = nil
1120
+ Fiber.schedule do
1121
+ ret = @io.read
1122
+ end
1123
+ @scheduler.join
1124
+ assert_equal "===", ret
1125
+ assert_equal({
1126
+ fiber: 1,
1127
+ io_read: 2,
1128
+ join: 1
1129
+ }, @scheduler.calls.map { it[:sym] }.tally)
1130
+ end
1131
+
1132
+ def test_IO_i_readbyte
1133
+ ret = nil
1134
+ Fiber.schedule do
1135
+ ret = @io.readbyte
1136
+ end
1137
+ @scheduler.join
1138
+ assert_equal 61, ret
1139
+ assert_equal({
1140
+ fiber: 1,
1141
+ io_read: 1,
1142
+ join: 1
1143
+ }, @scheduler.calls.map { it[:sym] }.tally)
1144
+ end
1145
+
1146
+ def test_IO_i_readchar
1147
+ ret = nil
1148
+ Fiber.schedule do
1149
+ ret = @io.readchar
1150
+ end
1151
+ @scheduler.join
1152
+ assert_equal '=', ret
1153
+ assert_equal({
1154
+ fiber: 1,
1155
+ io_read: 1,
1156
+ join: 1
1157
+ }, @scheduler.calls.map { it[:sym] }.tally)
1158
+ end
1159
+
1160
+ def test_IO_i_readline
1161
+ ret = nil
1162
+ Fiber.schedule do
1163
+ ret = @io.readline
1164
+ end
1165
+ @scheduler.join
1166
+ assert_equal '===', ret
1167
+ assert_equal({
1168
+ fiber: 1,
1169
+ io_read: 2,
1170
+ join: 1
1171
+ }, @scheduler.calls.map { it[:sym] }.tally)
1172
+ end
1173
+
1174
+ def test_IO_i_readlines
1175
+ ret = nil
1176
+ Fiber.schedule do
1177
+ ret = @io.readlines
1178
+ end
1179
+ @scheduler.join
1180
+ assert_equal ["==="], ret
1181
+ assert_equal({
1182
+ fiber: 1,
1183
+ io_read: 3,
1184
+ join: 1
1185
+ }, @scheduler.calls.map { it[:sym] }.tally)
1186
+ end
1187
+
1188
+ def test_IO_i_readpartial
1189
+ ret = nil
1190
+ Fiber.schedule do
1191
+ ret = @io.readpartial(7)
1192
+ end
1193
+ @scheduler.join
1194
+ assert_equal "===", ret
1195
+ assert_equal({
1196
+ fiber: 1,
1197
+ io_read: 1,
1198
+ join: 1
1199
+ }, @scheduler.calls.map { it[:sym] }.tally)
1200
+ end
1201
+
1202
+ def test_IO_i_sysread
1203
+ ret = nil
1204
+ Fiber.schedule do
1205
+ ret = @io.sysread(7)
1206
+ end
1207
+ @scheduler.join
1208
+ assert_equal "===", ret
1209
+ assert_equal({
1210
+ fiber: 1,
1211
+ io_read: 1,
1212
+ join: 1
1213
+ }, @scheduler.calls.map { it[:sym] }.tally)
1214
+ end
1215
+
1216
+ def test_IO_i_syswrite
1217
+ ret = nil
1218
+ Fiber.schedule do
1219
+ ret = @io.syswrite('2')
1220
+ end
1221
+ @scheduler.join
1222
+ assert_equal 1, ret
1223
+ assert_equal '2==', IO.read(@fn)
1224
+ assert_equal({
1225
+ fiber: 1,
1226
+ io_write: 1,
1227
+ join: 1
1228
+ }, @scheduler.calls.map { it[:sym] }.tally)
1229
+ end
1230
+
1231
+ def test_IO_i_wait
1232
+ ret = nil
1233
+ Fiber.schedule do
1234
+ ret = @io.wait(IO::READABLE | IO::WRITABLE)
1235
+ end
1236
+ @scheduler.join
1237
+ assert_equal @io, ret
1238
+ assert_equal({
1239
+ fiber: 1,
1240
+ io_wait: 1,
1241
+ join: 1
1242
+ }, @scheduler.calls.map { it[:sym] }.tally)
1243
+ end
1244
+
1245
+ def test_IO_i_wait_pipe_read_timeout
1246
+ r, w = IO.pipe
1247
+ ret = nil
1248
+ Fiber.schedule do
1249
+ ret = r.wait(IO::READABLE, 0.05)
1250
+ end
1251
+ @scheduler.join
1252
+ assert_nil ret
1253
+ assert_equal({
1254
+ fiber: 1,
1255
+ io_wait: 1,
1256
+ join: 1
1257
+ }, @scheduler.calls.map { it[:sym] }.tally)
1258
+ ensure
1259
+ r.close rescue nil
1260
+ w.close rescue nil
1261
+ end
1262
+
1263
+ def test_IO_i_wait_readable
1264
+ ret = nil
1265
+ Fiber.schedule do
1266
+ ret = @io.wait_readable
1267
+ end
1268
+ @scheduler.join
1269
+ assert_equal @io, ret
1270
+ assert_equal({
1271
+ fiber: 1,
1272
+ io_wait: 1,
1273
+ join: 1
1274
+ }, @scheduler.calls.map { it[:sym] }.tally)
1275
+ end
1276
+
1277
+ def test_IO_i_wait_writable
1278
+ ret = nil
1279
+ Fiber.schedule do
1280
+ ret = @io.wait_writable
1281
+ end
1282
+ @scheduler.join
1283
+ assert_equal @io, ret
1284
+ assert_equal({
1285
+ fiber: 1,
1286
+ io_wait: 1,
1287
+ join: 1
1288
+ }, @scheduler.calls.map { it[:sym] }.tally)
1289
+ end
1290
+
1291
+ def test_IO_i_write
1292
+ ret = nil
1293
+ Fiber.schedule do
1294
+ ret = @io.write('abcde')
1295
+ end
1296
+ @scheduler.join
1297
+ assert_equal 5, ret
1298
+ assert_equal 'abcde', IO.read(@fn)
1299
+ assert_equal({
1300
+ fiber: 1,
1301
+ io_write: 1,
1302
+ join: 1
1303
+ }, @scheduler.calls.map { it[:sym] }.tally)
1304
+ end
1305
+ end
1306
+
1307
+ class FiberSchedulerQueueTest < UMBaseTest
1308
+ def setup
1309
+ super
1310
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
1311
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
1312
+ Fiber.set_scheduler(@scheduler)
1313
+ @queue = Queue.new
1314
+ end
1315
+
1316
+ def teardown
1317
+ Fiber.set_scheduler(nil)
1318
+ super
1319
+ end
1320
+
1321
+ I = 5
1322
+ C = 5
1323
+
1324
+ def test_fiber_scheduler_queue_multi_fiber
1325
+ C.times {
1326
+ Fiber.schedule do
1327
+ I.times { sleep 0.0001; @queue << rand(1000) }
1328
+ end
1329
+ }
1330
+ C.times {
1331
+ Fiber.schedule do
1332
+ I.times { @queue.shift; @scheduler.yield; }
1333
+ end
1334
+ }
1335
+ @scheduler.join
1336
+ assert_equal({
1337
+ fiber: 10,
1338
+ kernel_sleep: 25,
1339
+ yield: 25,
1340
+ block: 25,
1341
+ unblock: 25,
1342
+ join: 1
1343
+ }, @scheduler.calls.map { it[:sym] }.tally)
1344
+ end
1345
+ end
1346
+
1347
+ class FiberSchedulerNetHTTPTest < UMBaseTest
1348
+ def setup
1349
+ super
1350
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
1351
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
1352
+ Fiber.set_scheduler(@scheduler)
1353
+ @uri = URI('https://ipinfo.io/')
1354
+ end
1355
+
1356
+ def teardown
1357
+ Fiber.set_scheduler(nil)
1358
+ super
1359
+ end
1360
+
1361
+ C = 10
1362
+
1363
+ def test_fiber_scheduler_net_http
1364
+ buf = []
1365
+ C.times {
1366
+ Fiber.schedule do
1367
+ buf << JSON.parse(Net::HTTP.get(@uri))
1368
+ end
1369
+ }
1370
+ @scheduler.join
1371
+ assert_equal C, buf.size
1372
+ assert_equal 1, buf.map { it['ip'] }.uniq.compact.size
1373
+ calls = @scheduler.calls.map { it[:sym] }.tally
1374
+ assert_equal C, calls[:fiber]
1375
+ assert_equal C, calls[:io_close]
1376
+ assert_in_range (C * 2)..(C * 4), calls[:io_wait]
1377
+ assert_in_range (C * 7)..(C * 17), calls[:blocking_operation_wait]
1378
+ end
1379
+ end
1380
+
1381
+ class FiberSchedulerMultiPipeTest < UMBaseTest
1382
+ def setup
1383
+ super
1384
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
1385
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
1386
+ Fiber.set_scheduler(@scheduler)
1387
+ @uri = URI('https://ipinfo.io/')
1388
+ end
1389
+
1390
+ def teardown
1391
+ Fiber.set_scheduler(nil)
1392
+ super
1393
+ end
1394
+
1395
+ C = 10
1396
+ I = 100
1397
+ SIZE = 1024
1398
+ DATA = '*' * SIZE
1399
+
1400
+ def test_fiber_scheduler_multi_pipe
1401
+ count = 0
1402
+ ios = []
1403
+ C.times do
1404
+ r, w = IO.pipe
1405
+ r.sync = true
1406
+ w.sync = true
1407
+ ios << r << w
1408
+ Fiber.schedule do
1409
+ I.times { w.write(DATA); count += 1 }
1410
+ w.close
1411
+ end
1412
+ Fiber.schedule do
1413
+ I.times { r.read(SIZE); count += 1 }
1414
+ end
1415
+ end
1416
+ @scheduler.join
1417
+ assert_equal 2000, count
1418
+ assert_equal({
1419
+ fiber: 20,
1420
+ io_read: 1000,
1421
+ io_write: 1000,
1422
+ io_close: 10,
1423
+ join: 1
1424
+ }, @scheduler.calls.map { it[:sym] }.tally)
1425
+ ensure
1426
+ ios.each { it.close rescue nil }
1427
+ end
1428
+ end
1429
+
1430
+ class FiberSchedulerMultiTCPTest < UMBaseTest
1431
+ C = 10
1432
+ W = 10
1433
+ I = 10
1434
+ SIZE = 1024
1435
+ DATA = '*' * SIZE
1436
+
1437
+ def p(o)
1438
+ # UM.debug(o.inspect)
1439
+ end
1440
+
1441
+ def run_server(port)
1442
+ server = TCPServer.new('127.0.0.1', port)
1443
+ p(server:)
1444
+
1445
+ server_fiber = Fiber.schedule { accept_loop(server) }
1446
+
1447
+ fibers = []
1448
+ W.times {
1449
+ fibers << Fiber.schedule { run_client(port) }
1450
+ }
1451
+ @machine.await_fibers(fibers)
1452
+ server.close
1453
+ @machine.await_fibers(server_fiber)
1454
+ rescue Exception => e
1455
+ p test_run_server: e
1456
+ p e.backtrace
1457
+ exit!
1458
+ end
1459
+
1460
+ def accept_loop(server)
1461
+ loop do
1462
+ conn = server.accept
1463
+ p(accept: conn)
1464
+ Fiber.schedule { conn_loop(conn) }
1465
+ end
1466
+ rescue IOError
1467
+ # socket closed
1468
+ rescue Exception => e
1469
+ p accept_loop: e
1470
+ p e.backtrace
1471
+ exit!
1472
+ end
1473
+
1474
+ def conn_loop(conn)
1475
+ loop do
1476
+ msg = conn.recv(SIZE * 2)
1477
+ break if !msg
1478
+
1479
+ conn.send(msg, 0)
1480
+ end
1481
+ rescue Exception => e
1482
+ p conn_loop: e
1483
+ p e.backtrace
1484
+ ensure
1485
+ conn.close rescue nil
1486
+ end
1487
+
1488
+ def run_client(port)
1489
+ client = TCPSocket.new('127.0.0.1', port)
1490
+ p(client:)
1491
+ I.times do
1492
+ client.send(DATA, 0)
1493
+ client.recv(SIZE * 2)
1494
+ @msg_count += 1
1495
+ end
1496
+ p(:client_done)
1497
+ rescue Exception => e
1498
+ p client_loop: e
1499
+ p e.backtrace
1500
+ ensure
1501
+ client.close
1502
+ end
1503
+
1504
+ def run_fiber_scheduler_multi_tcp
1505
+ @msg_count = 0
1506
+ STDOUT.sync = true
1507
+ ios = []
1508
+ fibers = []
1509
+ C.times do |i|
1510
+ port = @port_base + i
1511
+ fibers << Fiber.schedule do
1512
+ run_server(port)
1513
+ end
1514
+ end
1515
+ @machine.await_fibers(fibers)
1516
+ @calls = @scheduler.calls.map { it[:sym] }.tally
1517
+ ensure
1518
+ # p ensure: ios
1519
+ ios.each { it.close rescue nil }
1520
+ end
1521
+
1522
+ def test_fiber_scheduler_multi_tcp
1523
+ Thread.new do
1524
+ @raw_scheduler = UM::FiberScheduler.new(@machine)
1525
+ @scheduler = MethodCallAuditor.new(@raw_scheduler)
1526
+ Fiber.set_scheduler(@scheduler)
1527
+ @port_base = assign_port
1528
+ run_fiber_scheduler_multi_tcp
1529
+ ensure
1530
+ Fiber.set_scheduler(nil)
1531
+ end.join
1532
+
1533
+ assert_equal 1000, @msg_count
1534
+ assert_equal({
1535
+ fiber: 220,
1536
+ io_wait: 2120,
1537
+ io_close: 210,
1538
+ fiber_interrupt: 10,
1539
+ unblock: 10,
1540
+ kernel_sleep: 10
1541
+ }, @calls)
1542
+ end
138
1543
  end