polyphony 0.45.2 → 0.47.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +39 -0
  5. data/Gemfile.lock +3 -3
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +20 -28
  9. data/bin/test +4 -0
  10. data/examples/core/enumerable.rb +64 -0
  11. data/examples/io/raw.rb +14 -0
  12. data/examples/io/reline.rb +18 -0
  13. data/examples/performance/fiber_resume.rb +43 -0
  14. data/examples/performance/fiber_transfer.rb +13 -4
  15. data/examples/performance/multi_snooze.rb +0 -1
  16. data/examples/performance/thread-vs-fiber/compare.rb +59 -0
  17. data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
  19. data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
  20. data/examples/performance/thread_switch.rb +44 -0
  21. data/ext/liburing/liburing.h +585 -0
  22. data/ext/liburing/liburing/README.md +4 -0
  23. data/ext/liburing/liburing/barrier.h +73 -0
  24. data/ext/liburing/liburing/compat.h +15 -0
  25. data/ext/liburing/liburing/io_uring.h +343 -0
  26. data/ext/liburing/queue.c +333 -0
  27. data/ext/liburing/register.c +187 -0
  28. data/ext/liburing/setup.c +210 -0
  29. data/ext/liburing/syscall.c +54 -0
  30. data/ext/liburing/syscall.h +18 -0
  31. data/ext/polyphony/backend.h +1 -15
  32. data/ext/polyphony/backend_common.h +129 -0
  33. data/ext/polyphony/backend_io_uring.c +995 -0
  34. data/ext/polyphony/backend_io_uring_context.c +74 -0
  35. data/ext/polyphony/backend_io_uring_context.h +53 -0
  36. data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
  37. data/ext/polyphony/event.c +1 -1
  38. data/ext/polyphony/extconf.rb +31 -13
  39. data/ext/polyphony/fiber.c +60 -32
  40. data/ext/polyphony/libev.c +4 -0
  41. data/ext/polyphony/libev.h +8 -2
  42. data/ext/polyphony/liburing.c +8 -0
  43. data/ext/polyphony/playground.c +51 -0
  44. data/ext/polyphony/polyphony.c +9 -6
  45. data/ext/polyphony/polyphony.h +35 -19
  46. data/ext/polyphony/polyphony_ext.c +12 -4
  47. data/ext/polyphony/queue.c +100 -35
  48. data/ext/polyphony/runqueue.c +102 -0
  49. data/ext/polyphony/runqueue_ring_buffer.c +85 -0
  50. data/ext/polyphony/runqueue_ring_buffer.h +31 -0
  51. data/ext/polyphony/thread.c +42 -90
  52. data/lib/polyphony.rb +2 -2
  53. data/lib/polyphony/adapters/process.rb +0 -3
  54. data/lib/polyphony/adapters/trace.rb +2 -2
  55. data/lib/polyphony/core/exceptions.rb +0 -4
  56. data/lib/polyphony/core/global_api.rb +47 -23
  57. data/lib/polyphony/core/sync.rb +7 -5
  58. data/lib/polyphony/extensions/core.rb +14 -33
  59. data/lib/polyphony/extensions/debug.rb +13 -0
  60. data/lib/polyphony/extensions/fiber.rb +21 -3
  61. data/lib/polyphony/extensions/io.rb +15 -4
  62. data/lib/polyphony/extensions/openssl.rb +6 -0
  63. data/lib/polyphony/extensions/socket.rb +63 -10
  64. data/lib/polyphony/version.rb +1 -1
  65. data/polyphony.gemspec +1 -1
  66. data/test/helper.rb +36 -4
  67. data/test/io_uring_test.rb +55 -0
  68. data/test/stress.rb +4 -1
  69. data/test/test_backend.rb +63 -6
  70. data/test/test_ext.rb +1 -2
  71. data/test/test_fiber.rb +55 -20
  72. data/test/test_global_api.rb +132 -31
  73. data/test/test_io.rb +42 -0
  74. data/test/test_queue.rb +117 -0
  75. data/test/test_signal.rb +11 -8
  76. data/test/test_socket.rb +2 -2
  77. data/test/test_sync.rb +21 -0
  78. data/test/test_throttler.rb +3 -6
  79. data/test/test_trace.rb +7 -5
  80. metadata +36 -6
@@ -183,8 +183,7 @@ class TimeoutTest < MiniTest::Test
183
183
  end
184
184
 
185
185
  def test_that_timeout_method_accepts_custom_error_class_and_message
186
- buffer = []
187
- spin { 3.times { |i| buffer << i; snooze } }
186
+ e = nil
188
187
  begin
189
188
  Timeout.timeout(0.05, MyTimeout, 'foo') { sleep 1 }
190
189
  rescue Exception => e
@@ -62,7 +62,7 @@ class FiberTest < MiniTest::Test
62
62
  assert_equal 0, Fiber.current.children.size
63
63
  end
64
64
 
65
- def test_await_from_multiple_fibers_with_interruption
65
+ def test_await_from_multiple_fibers_with_interruption=
66
66
  buffer = []
67
67
  f1 = spin {
68
68
  sleep 0.02
@@ -128,15 +128,16 @@ class FiberTest < MiniTest::Test
128
128
  worker&.join
129
129
  end
130
130
 
131
- def test_ev_loop_anti_starve_mechanism
132
- async = Polyphony::Event.new
131
+ def test_backend_wakeup_mechanism
132
+ event = Polyphony::Event.new
133
+
133
134
  t = Thread.new do
134
135
  f = spin_loop { snooze }
135
136
  sleep 0.001
136
- async.signal(:foo)
137
+ event.signal(:foo)
137
138
  end
138
139
 
139
- result = move_on_after(1) { async.await }
140
+ result = move_on_after(1) { event.await }
140
141
 
141
142
  assert_equal :foo, result
142
143
  ensure
@@ -469,12 +470,14 @@ class FiberTest < MiniTest::Test
469
470
  end
470
471
  snooze # allow nested fiber to run before finishing
471
472
  end
472
- suspend
473
- rescue Exception => e
474
- raised_error = e
475
- ensure
476
- assert raised_error
477
- assert_equal 'foo', raised_error.message
473
+ begin
474
+ suspend
475
+ rescue Exception => e
476
+ raised_error = e
477
+ ensure
478
+ assert raised_error
479
+ assert_equal 'foo', raised_error.message
480
+ end
478
481
  end
479
482
 
480
483
  def test_await_multiple_fibers
@@ -562,14 +565,14 @@ class FiberTest < MiniTest::Test
562
565
  end
563
566
 
564
567
  def test_inspect
565
- expected = format('#<Fiber:%s (root) (running)>', Fiber.current.object_id)
568
+ expected = format('#<Fiber main:%s (root) (running)>', Fiber.current.object_id)
566
569
  assert_equal expected, Fiber.current.inspect
567
570
 
568
571
  spin_line_no = __LINE__ + 1
569
- f = spin { :foo }
572
+ f = spin(:baz) { :foo }
570
573
 
571
574
  expected = format(
572
- '#<Fiber:%s %s:%d:in `test_inspect\' (runnable)>',
575
+ '#<Fiber baz:%s %s:%d:in `test_inspect\' (runnable)>',
573
576
  f.object_id,
574
577
  __FILE__,
575
578
  spin_line_no
@@ -578,7 +581,7 @@ class FiberTest < MiniTest::Test
578
581
 
579
582
  f.await
580
583
  expected = format(
581
- '#<Fiber:%s %s:%d:in `test_inspect\' (dead)>',
584
+ '#<Fiber baz:%s %s:%d:in `test_inspect\' (dead)>',
582
585
  f.object_id,
583
586
  __FILE__,
584
587
  spin_line_no
@@ -631,11 +634,12 @@ class FiberTest < MiniTest::Test
631
634
  def test_signal_handling_int
632
635
  i, o = IO.pipe
633
636
  pid = Polyphony.fork do
634
- f = spin { sleep 100 }
637
+ f = spin { sleep 3 }
635
638
  begin
636
639
  i.close
637
640
  f.await
638
641
  rescue Exception => e
642
+ trace e
639
643
  o << e.class.name
640
644
  o.close
641
645
  end
@@ -653,7 +657,7 @@ class FiberTest < MiniTest::Test
653
657
  def test_signal_handling_term
654
658
  i, o = IO.pipe
655
659
  pid = Polyphony.fork do
656
- f = spin { sleep 100 }
660
+ f = spin { sleep 3 }
657
661
  begin
658
662
  i.close
659
663
  f.await
@@ -662,7 +666,7 @@ class FiberTest < MiniTest::Test
662
666
  o.close
663
667
  end
664
668
  end
665
- sleep 0.2
669
+ sleep 0.1
666
670
  f = spin { Thread.current.backend.waitpid(pid) }
667
671
  o.close
668
672
  Process.kill('TERM', pid)
@@ -677,7 +681,7 @@ class FiberTest < MiniTest::Test
677
681
  pid = Polyphony.fork do
678
682
  i.close
679
683
  spin do
680
- sleep 100
684
+ sleep 3
681
685
  rescue Exception => e
682
686
  o << e.class.to_s
683
687
  o.close
@@ -687,7 +691,7 @@ class FiberTest < MiniTest::Test
687
691
  end
688
692
  o.close
689
693
  spin do
690
- sleep 0.2
694
+ sleep 0.1
691
695
  Process.kill('TERM', pid)
692
696
  end
693
697
  Thread.current.backend.waitpid(pid)
@@ -703,6 +707,7 @@ class FiberTest < MiniTest::Test
703
707
  assert_nil f.thread
704
708
  snooze
705
709
  f.setup_raw
710
+
706
711
  assert_equal Thread.current, f.thread
707
712
  assert_nil f.parent
708
713
 
@@ -711,6 +716,7 @@ class FiberTest < MiniTest::Test
711
716
  f << 'bar'
712
717
  snooze
713
718
  assert_equal ['bar'], buffer
719
+ snooze
714
720
  end
715
721
  end
716
722
 
@@ -732,6 +738,35 @@ class MailboxTest < MiniTest::Test
732
738
  f&.stop
733
739
  end
734
740
 
741
+ def test_capped_fiber_mailbox
742
+ buffer = []
743
+ a = spin_loop do
744
+ 3.times { snooze }
745
+ buffer << [:receive, receive]
746
+ end
747
+ a.mailbox.cap(1)
748
+
749
+ b = spin do
750
+ (1..3).each do |i|
751
+ a << i
752
+ buffer << [:send, i]
753
+ end
754
+ end
755
+
756
+ (1..10).each do |i|
757
+ snooze
758
+ buffer << [:snooze, i]
759
+ end
760
+
761
+ b.join
762
+
763
+ assert_equal [
764
+ [:snooze, 1], [:send, 1], [:snooze, 2], [:snooze, 3], [:snooze, 4],
765
+ [:receive, 1], [:snooze, 5], [:send, 2], [:snooze, 6], [:snooze, 7],
766
+ [:snooze, 8], [:receive, 2], [:snooze, 9], [:send, 3], [:snooze, 10]
767
+ ], buffer
768
+ end
769
+
735
770
  def test_that_multiple_messages_sent_at_once_arrive_in_order
736
771
  msgs = []
737
772
  f = spin { loop { msgs << receive } }
@@ -72,28 +72,32 @@ class ExceptionTest < MiniTest::Test
72
72
  rescue Exception => e
73
73
  frames << 3
74
74
  raise e
75
- end#.await
76
- 5.times { snooze }
77
- rescue Exception => e
78
- error = e
79
- ensure
80
- assert_kind_of RuntimeError, error
81
- assert_equal [2, 3], frames
75
+ end
76
+ begin
77
+ 5.times { snooze }
78
+ rescue Exception => e
79
+ error = e
80
+ ensure
81
+ assert_kind_of RuntimeError, error
82
+ assert_equal [2, 3], frames
83
+ end
82
84
  end
83
85
 
84
86
  def test_cross_fiber_backtrace_with_dead_calling_fiber
85
87
  error = nil
86
- spin do
88
+ begin
87
89
  spin do
88
90
  spin do
89
- raise 'foo'
91
+ spin do
92
+ raise 'foo'
93
+ end.await
90
94
  end.await
91
95
  end.await
92
- end.await
93
- rescue Exception => e
94
- error = e
95
- ensure
96
- assert_kind_of RuntimeError, error
96
+ rescue Exception => e
97
+ error = e
98
+ ensure
99
+ assert_kind_of RuntimeError, error
100
+ end
97
101
  end
98
102
  end
99
103
 
@@ -122,6 +126,20 @@ class MoveOnAfterTest < MiniTest::Test
122
126
  assert_equal :bar, v
123
127
  end
124
128
 
129
+ def test_move_on_after_with_reset
130
+ t0 = Time.now
131
+ v = move_on_after(0.01, with_value: :moved_on) do |timeout|
132
+ sleep 0.007
133
+ timeout.reset
134
+ sleep 0.007
135
+ nil
136
+ end
137
+ t1 = Time.now
138
+
139
+ assert_nil v
140
+ assert_in_range 0.014..0.02, t1 - t0
141
+ end
142
+
125
143
  def test_move_on_after_without_block
126
144
  t0 = Time.now
127
145
  f = move_on_after(0.01, with_value: 'foo')
@@ -132,6 +150,28 @@ class MoveOnAfterTest < MiniTest::Test
132
150
  assert t1 - t0 < 0.1
133
151
  assert_equal 'foo', v
134
152
  end
153
+
154
+ def test_nested_move_on_after
155
+ t0 = Time.now
156
+ o = move_on_after(0.01, with_value: 1) do
157
+ move_on_after(0.02, with_value: 2) do
158
+ sleep 1
159
+ end
160
+ end
161
+ t1 = Time.now
162
+ assert_equal 1, o
163
+ assert_in_range 0.008..0.013, t1 - t0
164
+
165
+ t0 = Time.now
166
+ o = move_on_after(0.02, with_value: 1) do
167
+ move_on_after(0.01, with_value: 2) do
168
+ sleep 1
169
+ end
170
+ end
171
+ t1 = Time.now
172
+ assert_equal 2, o
173
+ assert_in_range 0.008..0.013, t1 - t0
174
+ end
135
175
  end
136
176
 
137
177
  class CancelAfterTest < MiniTest::Test
@@ -160,6 +200,19 @@ class CancelAfterTest < MiniTest::Test
160
200
  assert t1 - t0 < 0.1
161
201
  end
162
202
 
203
+ def test_cancel_after_with_reset
204
+ t0 = Time.now
205
+ cancel_after(0.01) do |f|
206
+ assert_kind_of Fiber, f
207
+ assert_equal Fiber.current, f.parent
208
+ sleep 0.007
209
+ f.reset
210
+ sleep 0.007
211
+ end
212
+ t1 = Time.now
213
+ assert_in_range 0.014..0.02, t1 - t0
214
+ end
215
+
163
216
  class CustomException < Exception
164
217
  end
165
218
 
@@ -171,6 +224,19 @@ class CancelAfterTest < MiniTest::Test
171
224
  end
172
225
  end
173
226
 
227
+ begin
228
+ err = nil
229
+ cancel_after(0.01, with_exception: [CustomException, 'custom message']) do
230
+ sleep 1
231
+ :foo
232
+ end
233
+ rescue Exception => err
234
+ ensure
235
+ assert_kind_of CustomException, err
236
+ assert_equal 'custom message', err.message
237
+ end
238
+
239
+
174
240
  begin
175
241
  e = nil
176
242
  cancel_after(0.01, with_exception: 'foo') do
@@ -226,12 +292,49 @@ class SpinLoopTest < MiniTest::Test
226
292
  buffer = []
227
293
  counter = 0
228
294
  t0 = Time.now
229
- f = spin_loop(rate: 10) { buffer << (counter += 1) }
230
- sleep 0.2
295
+ f = spin_loop(rate: 100) { buffer << (counter += 1) }
296
+ sleep 0.02
231
297
  f.stop
232
- elapsed = Time.now - t0
233
- expected = (elapsed * 10).to_i
234
- assert counter >= expected - 1 && counter <= expected + 1
298
+ assert_in_range 1..3, counter
299
+ end
300
+ end
301
+
302
+ class SpinScopeTest < MiniTest::Test
303
+ def test_spin_scope
304
+ queue = Queue.new
305
+ buffer = {}
306
+ spin do
307
+ queue << 1
308
+ snooze
309
+ queue << 2
310
+ end
311
+ f = nil
312
+ result = spin_scope do
313
+ f = Fiber.current
314
+ spin { buffer[:a] = queue.shift }
315
+ spin { buffer[:b] = queue.shift }
316
+ :foo
317
+ end
318
+ assert_equal :foo, result
319
+ assert_kind_of Fiber, f
320
+ assert_equal :dead, f.state
321
+ assert_equal ({a: 1, b: 2}), buffer
322
+ end
323
+
324
+ def test_spin_scope_with_exception
325
+ queue = Queue.new
326
+ buffer = []
327
+ spin do
328
+ spin_scope do
329
+ spin { buffer << queue.shift }
330
+ spin { raise 'foobar' }
331
+ end
332
+ rescue => e
333
+ buffer << e.message
334
+ end
335
+ 6.times { snooze }
336
+ assert_equal 0, Fiber.current.children.size
337
+ assert_equal ['foobar'], buffer
235
338
  end
236
339
  end
237
340
 
@@ -241,22 +344,22 @@ class ThrottledLoopTest < MiniTest::Test
241
344
  counter = 0
242
345
  t0 = Time.now
243
346
  f = spin do
244
- throttled_loop(10) { buffer << (counter += 1) }
347
+ throttled_loop(100) { buffer << (counter += 1) }
245
348
  end
246
- sleep 0.3
247
- f.stop
248
- elapsed = Time.now - t0
249
- expected = (elapsed * 10).to_i
250
- assert counter >= expected - 1 && counter <= expected + 1
349
+ sleep 0.03
350
+ assert_in_range 2..4, counter
251
351
  end
252
352
 
253
353
  def test_throttled_loop_with_count
254
354
  buffer = []
255
355
  counter = 0
356
+ t0 = Time.now
256
357
  f = spin do
257
358
  throttled_loop(50, count: 5) { buffer << (counter += 1) }
258
359
  end
259
360
  f.await
361
+ t1 = Time.now
362
+ assert_in_range 0.075..0.15, t1 - t0
260
363
  assert_equal [1, 2, 3, 4, 5], buffer
261
364
  end
262
365
  end
@@ -275,13 +378,11 @@ class GlobalAPIEtcTest < MiniTest::Test
275
378
  buffer = []
276
379
  t0 = Time.now
277
380
  f = spin do
278
- every(0.1) { buffer << 1 }
381
+ every(0.01) { buffer << 1 }
279
382
  end
280
- sleep 0.5
383
+ sleep 0.05
281
384
  f.stop
282
- elapsed = Time.now - t0
283
- expected = (elapsed / 0.1).to_i
284
- assert buffer.size >= expected - 2 && buffer.size <= expected + 2
385
+ assert_in_range 4..6, buffer.size
285
386
  end
286
387
 
287
388
  def test_sleep
@@ -349,4 +450,4 @@ class GlobalAPIEtcTest < MiniTest::Test
349
450
 
350
451
  assert_equal [0, 1, 2], values
351
452
  end
352
- end
453
+ end
@@ -92,6 +92,48 @@ class IOTest < MiniTest::Test
92
92
  assert_raises(EOFError) { i.readpartial(1) }
93
93
  end
94
94
 
95
+ def test_getc
96
+ i, o = IO.pipe
97
+
98
+ buf = []
99
+ f = spin do
100
+ while (c = i.getc)
101
+ buf << c
102
+ end
103
+ end
104
+
105
+ snooze
106
+ assert_equal [], buf
107
+
108
+ o << 'f'
109
+ snooze
110
+ o << 'g'
111
+ o.close
112
+ f.await
113
+ assert_equal ['f', 'g'], buf
114
+ end
115
+
116
+ def test_getbyte
117
+ i, o = IO.pipe
118
+
119
+ buf = []
120
+ f = spin do
121
+ while (b = i.getbyte)
122
+ buf << b
123
+ end
124
+ end
125
+
126
+ snooze
127
+ assert_equal [], buf
128
+
129
+ o << 'f'
130
+ snooze
131
+ o << 'g'
132
+ o.close
133
+ f.await
134
+ assert_equal [102, 103], buf
135
+ end
136
+
95
137
  # see https://github.com/digital-fabric/polyphony/issues/30
96
138
  def test_reopened_tempfile
97
139
  file = Tempfile.new