polyphony 0.45.4 → 0.47.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +32 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +20 -34
  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/multi_snooze.rb +0 -1
  14. data/examples/performance/thread-vs-fiber/compare.rb +59 -0
  15. data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
  16. data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
  17. data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
  18. data/examples/performance/thread_switch.rb +44 -0
  19. data/ext/liburing/liburing.h +585 -0
  20. data/ext/liburing/liburing/README.md +4 -0
  21. data/ext/liburing/liburing/barrier.h +73 -0
  22. data/ext/liburing/liburing/compat.h +15 -0
  23. data/ext/liburing/liburing/io_uring.h +343 -0
  24. data/ext/liburing/queue.c +333 -0
  25. data/ext/liburing/register.c +187 -0
  26. data/ext/liburing/setup.c +210 -0
  27. data/ext/liburing/syscall.c +54 -0
  28. data/ext/liburing/syscall.h +18 -0
  29. data/ext/polyphony/backend.h +1 -15
  30. data/ext/polyphony/backend_common.h +129 -0
  31. data/ext/polyphony/backend_io_uring.c +995 -0
  32. data/ext/polyphony/backend_io_uring_context.c +74 -0
  33. data/ext/polyphony/backend_io_uring_context.h +53 -0
  34. data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
  35. data/ext/polyphony/event.c +1 -1
  36. data/ext/polyphony/extconf.rb +31 -13
  37. data/ext/polyphony/fiber.c +60 -32
  38. data/ext/polyphony/libev.c +4 -0
  39. data/ext/polyphony/libev.h +8 -2
  40. data/ext/polyphony/liburing.c +8 -0
  41. data/ext/polyphony/playground.c +51 -0
  42. data/ext/polyphony/polyphony.c +9 -6
  43. data/ext/polyphony/polyphony.h +35 -19
  44. data/ext/polyphony/polyphony_ext.c +12 -4
  45. data/ext/polyphony/queue.c +100 -35
  46. data/ext/polyphony/runqueue.c +102 -0
  47. data/ext/polyphony/runqueue_ring_buffer.c +85 -0
  48. data/ext/polyphony/runqueue_ring_buffer.h +31 -0
  49. data/ext/polyphony/thread.c +42 -90
  50. data/lib/polyphony/adapters/trace.rb +2 -2
  51. data/lib/polyphony/core/exceptions.rb +0 -4
  52. data/lib/polyphony/core/global_api.rb +47 -23
  53. data/lib/polyphony/core/resource_pool.rb +12 -1
  54. data/lib/polyphony/core/sync.rb +7 -5
  55. data/lib/polyphony/extensions/core.rb +9 -15
  56. data/lib/polyphony/extensions/debug.rb +13 -0
  57. data/lib/polyphony/extensions/fiber.rb +13 -9
  58. data/lib/polyphony/extensions/openssl.rb +6 -0
  59. data/lib/polyphony/extensions/socket.rb +68 -10
  60. data/lib/polyphony/version.rb +1 -1
  61. data/test/helper.rb +36 -4
  62. data/test/io_uring_test.rb +55 -0
  63. data/test/stress.rb +4 -1
  64. data/test/test_backend.rb +63 -6
  65. data/test/test_ext.rb +1 -2
  66. data/test/test_fiber.rb +55 -20
  67. data/test/test_global_api.rb +132 -31
  68. data/test/test_queue.rb +117 -0
  69. data/test/test_resource_pool.rb +21 -0
  70. data/test/test_socket.rb +2 -2
  71. data/test/test_sync.rb +21 -0
  72. data/test/test_throttler.rb +3 -6
  73. data/test/test_trace.rb +7 -5
  74. metadata +32 -4
@@ -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
@@ -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