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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile.lock +3 -3
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/TODO.md +20 -28
- data/bin/test +4 -0
- data/examples/core/enumerable.rb +64 -0
- data/examples/io/raw.rb +14 -0
- data/examples/io/reline.rb +18 -0
- data/examples/performance/fiber_resume.rb +43 -0
- data/examples/performance/fiber_transfer.rb +13 -4
- data/examples/performance/multi_snooze.rb +0 -1
- data/examples/performance/thread-vs-fiber/compare.rb +59 -0
- data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
- data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
- data/examples/performance/thread_switch.rb +44 -0
- data/ext/liburing/liburing.h +585 -0
- data/ext/liburing/liburing/README.md +4 -0
- data/ext/liburing/liburing/barrier.h +73 -0
- data/ext/liburing/liburing/compat.h +15 -0
- data/ext/liburing/liburing/io_uring.h +343 -0
- data/ext/liburing/queue.c +333 -0
- data/ext/liburing/register.c +187 -0
- data/ext/liburing/setup.c +210 -0
- data/ext/liburing/syscall.c +54 -0
- data/ext/liburing/syscall.h +18 -0
- data/ext/polyphony/backend.h +1 -15
- data/ext/polyphony/backend_common.h +129 -0
- data/ext/polyphony/backend_io_uring.c +995 -0
- data/ext/polyphony/backend_io_uring_context.c +74 -0
- data/ext/polyphony/backend_io_uring_context.h +53 -0
- data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/extconf.rb +31 -13
- data/ext/polyphony/fiber.c +60 -32
- data/ext/polyphony/libev.c +4 -0
- data/ext/polyphony/libev.h +8 -2
- data/ext/polyphony/liburing.c +8 -0
- data/ext/polyphony/playground.c +51 -0
- data/ext/polyphony/polyphony.c +9 -6
- data/ext/polyphony/polyphony.h +35 -19
- data/ext/polyphony/polyphony_ext.c +12 -4
- data/ext/polyphony/queue.c +100 -35
- data/ext/polyphony/runqueue.c +102 -0
- data/ext/polyphony/runqueue_ring_buffer.c +85 -0
- data/ext/polyphony/runqueue_ring_buffer.h +31 -0
- data/ext/polyphony/thread.c +42 -90
- data/lib/polyphony.rb +2 -2
- data/lib/polyphony/adapters/process.rb +0 -3
- data/lib/polyphony/adapters/trace.rb +2 -2
- data/lib/polyphony/core/exceptions.rb +0 -4
- data/lib/polyphony/core/global_api.rb +47 -23
- data/lib/polyphony/core/sync.rb +7 -5
- data/lib/polyphony/extensions/core.rb +14 -33
- data/lib/polyphony/extensions/debug.rb +13 -0
- data/lib/polyphony/extensions/fiber.rb +21 -3
- data/lib/polyphony/extensions/io.rb +15 -4
- data/lib/polyphony/extensions/openssl.rb +6 -0
- data/lib/polyphony/extensions/socket.rb +63 -10
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/helper.rb +36 -4
- data/test/io_uring_test.rb +55 -0
- data/test/stress.rb +4 -1
- data/test/test_backend.rb +63 -6
- data/test/test_ext.rb +1 -2
- data/test/test_fiber.rb +55 -20
- data/test/test_global_api.rb +132 -31
- data/test/test_io.rb +42 -0
- data/test/test_queue.rb +117 -0
- data/test/test_signal.rb +11 -8
- data/test/test_socket.rb +2 -2
- data/test/test_sync.rb +21 -0
- data/test/test_throttler.rb +3 -6
- data/test/test_trace.rb +7 -5
- metadata +36 -6
data/test/test_ext.rb
CHANGED
@@ -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
|
-
|
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
|
data/test/test_fiber.rb
CHANGED
@@ -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
|
132
|
-
|
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
|
-
|
137
|
+
event.signal(:foo)
|
137
138
|
end
|
138
139
|
|
139
|
-
result = move_on_after(1) {
|
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
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
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
|
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
|
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.
|
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
|
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.
|
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 } }
|
data/test/test_global_api.rb
CHANGED
@@ -72,28 +72,32 @@ class ExceptionTest < MiniTest::Test
|
|
72
72
|
rescue Exception => e
|
73
73
|
frames << 3
|
74
74
|
raise e
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
88
|
+
begin
|
87
89
|
spin do
|
88
90
|
spin do
|
89
|
-
|
91
|
+
spin do
|
92
|
+
raise 'foo'
|
93
|
+
end.await
|
90
94
|
end.await
|
91
95
|
end.await
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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:
|
230
|
-
sleep 0.
|
295
|
+
f = spin_loop(rate: 100) { buffer << (counter += 1) }
|
296
|
+
sleep 0.02
|
231
297
|
f.stop
|
232
|
-
|
233
|
-
|
234
|
-
|
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(
|
347
|
+
throttled_loop(100) { buffer << (counter += 1) }
|
245
348
|
end
|
246
|
-
sleep 0.
|
247
|
-
|
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.
|
381
|
+
every(0.01) { buffer << 1 }
|
279
382
|
end
|
280
|
-
sleep 0.
|
383
|
+
sleep 0.05
|
281
384
|
f.stop
|
282
|
-
|
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
|
data/test/test_io.rb
CHANGED
@@ -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
|