polyphony 0.45.5 → 0.47.2

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +23 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +21 -22
  9. data/bin/test +4 -0
  10. data/examples/core/enumerable.rb +64 -0
  11. data/examples/performance/fiber_resume.rb +43 -0
  12. data/examples/performance/fiber_transfer.rb +13 -4
  13. data/examples/performance/thread-vs-fiber/compare.rb +59 -0
  14. data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
  15. data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
  16. data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
  17. data/examples/performance/thread_switch.rb +44 -0
  18. data/ext/liburing/liburing.h +585 -0
  19. data/ext/liburing/liburing/README.md +4 -0
  20. data/ext/liburing/liburing/barrier.h +73 -0
  21. data/ext/liburing/liburing/compat.h +15 -0
  22. data/ext/liburing/liburing/io_uring.h +343 -0
  23. data/ext/liburing/queue.c +333 -0
  24. data/ext/liburing/register.c +187 -0
  25. data/ext/liburing/setup.c +210 -0
  26. data/ext/liburing/syscall.c +54 -0
  27. data/ext/liburing/syscall.h +18 -0
  28. data/ext/polyphony/backend.h +0 -14
  29. data/ext/polyphony/backend_common.h +129 -0
  30. data/ext/polyphony/backend_io_uring.c +995 -0
  31. data/ext/polyphony/backend_io_uring_context.c +74 -0
  32. data/ext/polyphony/backend_io_uring_context.h +53 -0
  33. data/ext/polyphony/{libev_backend.c → backend_libev.c} +304 -294
  34. data/ext/polyphony/event.c +1 -1
  35. data/ext/polyphony/extconf.rb +31 -13
  36. data/ext/polyphony/fiber.c +35 -24
  37. data/ext/polyphony/libev.c +4 -0
  38. data/ext/polyphony/libev.h +8 -2
  39. data/ext/polyphony/liburing.c +8 -0
  40. data/ext/polyphony/playground.c +51 -0
  41. data/ext/polyphony/polyphony.c +8 -5
  42. data/ext/polyphony/polyphony.h +23 -19
  43. data/ext/polyphony/polyphony_ext.c +10 -4
  44. data/ext/polyphony/queue.c +100 -35
  45. data/ext/polyphony/thread.c +10 -10
  46. data/lib/polyphony/adapters/trace.rb +2 -2
  47. data/lib/polyphony/core/exceptions.rb +0 -4
  48. data/lib/polyphony/core/global_api.rb +45 -21
  49. data/lib/polyphony/core/resource_pool.rb +12 -1
  50. data/lib/polyphony/extensions/core.rb +9 -15
  51. data/lib/polyphony/extensions/debug.rb +13 -0
  52. data/lib/polyphony/extensions/fiber.rb +8 -4
  53. data/lib/polyphony/extensions/openssl.rb +6 -0
  54. data/lib/polyphony/extensions/socket.rb +73 -10
  55. data/lib/polyphony/version.rb +1 -1
  56. data/test/helper.rb +36 -4
  57. data/test/io_uring_test.rb +55 -0
  58. data/test/stress.rb +4 -1
  59. data/test/test_backend.rb +63 -6
  60. data/test/test_ext.rb +1 -2
  61. data/test/test_fiber.rb +55 -20
  62. data/test/test_global_api.rb +107 -35
  63. data/test/test_queue.rb +117 -0
  64. data/test/test_resource_pool.rb +21 -0
  65. data/test/test_socket.rb +2 -2
  66. data/test/test_throttler.rb +3 -6
  67. data/test/test_trace.rb +7 -5
  68. metadata +28 -3
@@ -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
 
@@ -133,8 +137,7 @@ class MoveOnAfterTest < MiniTest::Test
133
137
  t1 = Time.now
134
138
 
135
139
  assert_nil v
136
- assert t1 - t0 >= 0.014
137
- assert t1 - t0 < 0.02
140
+ assert_in_range 0.014..0.02, t1 - t0
138
141
  end
139
142
 
140
143
  def test_move_on_after_without_block
@@ -147,6 +150,28 @@ class MoveOnAfterTest < MiniTest::Test
147
150
  assert t1 - t0 < 0.1
148
151
  assert_equal 'foo', v
149
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
150
175
  end
151
176
 
152
177
  class CancelAfterTest < MiniTest::Test
@@ -185,8 +210,7 @@ class CancelAfterTest < MiniTest::Test
185
210
  sleep 0.007
186
211
  end
187
212
  t1 = Time.now
188
- assert t1 - t0 >= 0.014
189
- assert t1 - t0 < 0.02
213
+ assert_in_range 0.014..0.02, t1 - t0
190
214
  end
191
215
 
192
216
  class CustomException < Exception
@@ -200,6 +224,19 @@ class CancelAfterTest < MiniTest::Test
200
224
  end
201
225
  end
202
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
+
203
240
  begin
204
241
  e = nil
205
242
  cancel_after(0.01, with_exception: 'foo') do
@@ -255,12 +292,49 @@ class SpinLoopTest < MiniTest::Test
255
292
  buffer = []
256
293
  counter = 0
257
294
  t0 = Time.now
258
- f = spin_loop(rate: 10) { buffer << (counter += 1) }
259
- sleep 0.2
295
+ f = spin_loop(rate: 100) { buffer << (counter += 1) }
296
+ sleep 0.02
260
297
  f.stop
261
- elapsed = Time.now - t0
262
- expected = (elapsed * 10).to_i
263
- 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
264
338
  end
265
339
  end
266
340
 
@@ -270,22 +344,22 @@ class ThrottledLoopTest < MiniTest::Test
270
344
  counter = 0
271
345
  t0 = Time.now
272
346
  f = spin do
273
- throttled_loop(10) { buffer << (counter += 1) }
347
+ throttled_loop(100) { buffer << (counter += 1) }
274
348
  end
275
- sleep 0.3
276
- f.stop
277
- elapsed = Time.now - t0
278
- expected = (elapsed * 10).to_i
279
- assert counter >= expected - 1 && counter <= expected + 1
349
+ sleep 0.03
350
+ assert_in_range 2..4, counter
280
351
  end
281
352
 
282
353
  def test_throttled_loop_with_count
283
354
  buffer = []
284
355
  counter = 0
356
+ t0 = Time.now
285
357
  f = spin do
286
358
  throttled_loop(50, count: 5) { buffer << (counter += 1) }
287
359
  end
288
360
  f.await
361
+ t1 = Time.now
362
+ assert_in_range 0.075..0.15, t1 - t0
289
363
  assert_equal [1, 2, 3, 4, 5], buffer
290
364
  end
291
365
  end
@@ -304,13 +378,11 @@ class GlobalAPIEtcTest < MiniTest::Test
304
378
  buffer = []
305
379
  t0 = Time.now
306
380
  f = spin do
307
- every(0.1) { buffer << 1 }
381
+ every(0.01) { buffer << 1 }
308
382
  end
309
- sleep 0.5
383
+ sleep 0.05
310
384
  f.stop
311
- elapsed = Time.now - t0
312
- expected = (elapsed / 0.1).to_i
313
- assert buffer.size >= expected - 2 && buffer.size <= expected + 2
385
+ assert_in_range 4..6, buffer.size
314
386
  end
315
387
 
316
388
  def test_sleep
@@ -378,4 +450,4 @@ class GlobalAPIEtcTest < MiniTest::Test
378
450
 
379
451
  assert_equal [0, 1, 2], values
380
452
  end
381
- end
453
+ end
@@ -129,4 +129,121 @@ class QueueTest < MiniTest::Test
129
129
 
130
130
  assert_equal 0, @queue.size
131
131
  end
132
+ end
133
+
134
+ class CappedQueueTest < MiniTest::Test
135
+ def setup
136
+ super
137
+ @queue = Polyphony::Queue.new
138
+ @queue.cap(3)
139
+ end
140
+
141
+ def test_capped?
142
+ q = Polyphony::Queue.new
143
+ assert_nil q.capped?
144
+
145
+ q.cap(3)
146
+ assert_equal 3, q.capped?
147
+ end
148
+
149
+ def test_initalize_with_cap
150
+ q = Polyphony::Queue.new(42)
151
+ assert_equal 42, q.capped?
152
+ end
153
+
154
+ def test_capped_push
155
+ buffer = []
156
+ a = spin do
157
+ (1..5).each do |i|
158
+ @queue.push(i)
159
+ buffer << :"p#{i}"
160
+ end
161
+ @queue.push :stop
162
+ end
163
+
164
+ snooze
165
+
166
+ b = spin_loop do
167
+ i = @queue.shift
168
+ raise Polyphony::Terminate if i == :stop
169
+ buffer << :"s#{i}"
170
+ end
171
+
172
+ Fiber.join(a, b)
173
+ assert_equal [:p1, :p2, :s1, :p3, :s2, :p4, :s3, :p5, :s4, :s5], buffer
174
+ end
175
+
176
+ def test_capped_multi_push
177
+ buffer = []
178
+ a = spin(:a) do
179
+ (1..3).each do |i|
180
+ @queue.push(i)
181
+ buffer << :"p#{i}"
182
+ end
183
+ end
184
+
185
+ buffer = []
186
+ b = spin(:b) do
187
+ (4..6).each do |i|
188
+ @queue.push(i)
189
+ buffer << :"p#{i}"
190
+ end
191
+ @queue.push :stop
192
+ end
193
+
194
+ c = spin_loop do
195
+ i = @queue.shift
196
+ raise Polyphony::Terminate if i == :stop
197
+ buffer << :"s#{i}"
198
+ snooze
199
+ end
200
+
201
+ Fiber.join(a, b, c)
202
+ assert_equal [:p1, :p4, :s1, :p5, :p2, :s4, :p3, :s5, :p6, :s2, :s3, :s6], buffer
203
+ end
204
+
205
+ def test_capped_clear
206
+ buffer = []
207
+ a = spin(:a) do
208
+ (1..5).each do |i|
209
+ @queue.push(i)
210
+ buffer << i
211
+ end
212
+ end
213
+
214
+ snooze while buffer.size < 3
215
+ @queue.clear
216
+ buffer << :clear
217
+
218
+ a.join
219
+ assert_equal [1, 2, 3, :clear, 4, 5], buffer
220
+ end
221
+
222
+ def test_capped_delete
223
+ buffer = []
224
+ a = spin(:a) do
225
+ (1..5).each do |i|
226
+ @queue.push(i)
227
+ buffer << i
228
+ end
229
+ end
230
+
231
+ i = 0
232
+ spin_loop do
233
+ i += 1
234
+ snooze
235
+ end
236
+
237
+ 5.times { snooze }
238
+ assert_equal 5, i
239
+ @queue.delete 1
240
+ buffer << :"d#{i}"
241
+ 3.times { snooze }
242
+ assert_equal 8, i
243
+ @queue.delete 2
244
+ buffer << :"d#{i}"
245
+
246
+ a.join
247
+ assert_equal [1, 2, 3, :d5, 4, :d8, 5], buffer
248
+ end
132
249
  end