polyphony 0.45.2 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
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