polyphony 0.46.1 → 0.47.4
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/CHANGELOG.md +21 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +37 -14
- data/examples/core/enumerable.rb +64 -0
- data/examples/io/unix_socket.rb +26 -0
- data/examples/performance/fiber_resume.rb +43 -0
- 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 +4 -3
- data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
- data/examples/performance/thread_switch.rb +44 -0
- data/ext/polyphony/backend_common.h +29 -0
- data/ext/polyphony/backend_io_uring.c +88 -13
- data/ext/polyphony/backend_io_uring_context.c +1 -0
- data/ext/polyphony/backend_io_uring_context.h +1 -0
- data/ext/polyphony/backend_libev.c +82 -15
- data/ext/polyphony/fiber.c +10 -1
- data/ext/polyphony/polyphony.c +3 -0
- data/ext/polyphony/polyphony.h +3 -6
- data/ext/polyphony/queue.c +99 -34
- data/lib/polyphony/core/global_api.rb +45 -32
- data/lib/polyphony/extensions/fiber.rb +8 -2
- data/lib/polyphony/extensions/socket.rb +74 -15
- data/lib/polyphony/version.rb +1 -1
- data/test/test_backend.rb +48 -0
- data/test/test_fiber.rb +33 -4
- data/test/test_global_api.rb +72 -1
- data/test/test_queue.rb +117 -0
- data/test/test_signal.rb +18 -0
- metadata +8 -2
@@ -16,16 +16,29 @@ module Polyphony
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
|
19
|
-
|
20
|
-
|
19
|
+
if !block
|
20
|
+
cancel_after_blockless_canceller(Fiber.current, interval, with_exception)
|
21
|
+
elsif block.arity > 0
|
22
|
+
cancel_after_with_block(Fiber.current, interval, with_exception, &block)
|
23
|
+
else
|
24
|
+
Thread.current.backend.timeout(interval, with_exception, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cancel_after_blockless_canceller(fiber, interval, with_exception)
|
29
|
+
spin do
|
21
30
|
sleep interval
|
22
31
|
exception = cancel_exception(with_exception)
|
23
|
-
# we don't want the cancelling fiber caller location as part of the
|
24
|
-
# exception backtrace
|
25
32
|
exception.__raising_fiber__ = nil
|
26
33
|
fiber.schedule exception
|
27
34
|
end
|
28
|
-
|
35
|
+
end
|
36
|
+
|
37
|
+
def cancel_after_with_block(fiber, interval, with_exception, &block)
|
38
|
+
canceller = cancel_after_blockless_canceller(fiber, interval, with_exception)
|
39
|
+
block.call(canceller)
|
40
|
+
ensure
|
41
|
+
canceller.stop
|
29
42
|
end
|
30
43
|
|
31
44
|
def cancel_exception(exception)
|
@@ -36,49 +49,49 @@ module Polyphony
|
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
39
|
-
def cancel_after_wrap_block(canceller, &block)
|
40
|
-
block.call(canceller)
|
41
|
-
ensure
|
42
|
-
canceller.stop
|
43
|
-
end
|
44
|
-
|
45
52
|
def spin(tag = nil, &block)
|
46
53
|
Fiber.current.spin(tag, caller, &block)
|
47
54
|
end
|
48
55
|
|
49
|
-
def spin_loop(tag = nil, rate: nil, &block)
|
50
|
-
if rate
|
56
|
+
def spin_loop(tag = nil, rate: nil, interval: nil, &block)
|
57
|
+
if rate || interval
|
51
58
|
Fiber.current.spin(tag, caller) do
|
52
|
-
throttled_loop(rate, &block)
|
59
|
+
throttled_loop(rate: rate, interval: interval, &block)
|
53
60
|
end
|
54
61
|
else
|
55
62
|
Fiber.current.spin(tag, caller) { loop(&block) }
|
56
63
|
end
|
57
64
|
end
|
58
65
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
def spin_scope
|
67
|
+
raise unless block_given?
|
68
|
+
|
69
|
+
spin do
|
70
|
+
result = yield
|
71
|
+
Fiber.current.await_all_children
|
72
|
+
result
|
73
|
+
end.await
|
74
|
+
end
|
75
|
+
|
76
|
+
def every(interval, &block)
|
77
|
+
Thread.current.backend.timer_loop(interval, &block)
|
70
78
|
end
|
71
79
|
|
72
80
|
def move_on_after(interval, with_value: nil, &block)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
81
|
+
if !block
|
82
|
+
move_on_blockless_canceller(Fiber.current, interval, with_value)
|
83
|
+
elsif block.arity > 0
|
84
|
+
move_on_after_with_block(Fiber.current, interval, with_value, &block)
|
85
|
+
else
|
86
|
+
Thread.current.backend.timeout(interval, nil, with_value, &block)
|
79
87
|
end
|
88
|
+
end
|
80
89
|
|
81
|
-
|
90
|
+
def move_on_blockless_canceller(fiber, interval, with_value)
|
91
|
+
spin do
|
92
|
+
sleep interval
|
93
|
+
fiber.schedule with_value
|
94
|
+
end
|
82
95
|
end
|
83
96
|
|
84
97
|
def move_on_after_with_block(fiber, interval, with_value, &block)
|
@@ -173,6 +173,7 @@ module Polyphony
|
|
173
173
|
# signals (see also the patched `Kernel#trap`)
|
174
174
|
def schedule_priority_oob_fiber(&block)
|
175
175
|
f = Fiber.new do
|
176
|
+
Fiber.current.setup_raw
|
176
177
|
block.call
|
177
178
|
rescue Exception => e
|
178
179
|
Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
|
@@ -262,6 +263,7 @@ module Polyphony
|
|
262
263
|
# allows the fiber to be scheduled and to receive messages.
|
263
264
|
def setup_raw
|
264
265
|
@thread = Thread.current
|
266
|
+
@running = true
|
265
267
|
end
|
266
268
|
|
267
269
|
def setup_main_fiber
|
@@ -328,14 +330,18 @@ class ::Fiber
|
|
328
330
|
extend Polyphony::FiberControlClassMethods
|
329
331
|
|
330
332
|
attr_accessor :tag, :thread, :parent
|
331
|
-
attr_reader :result
|
333
|
+
attr_reader :result
|
332
334
|
|
333
335
|
def running?
|
334
336
|
@running
|
335
337
|
end
|
336
338
|
|
337
339
|
def inspect
|
338
|
-
|
340
|
+
if @tag
|
341
|
+
"#<Fiber #{tag}:#{object_id} #{location} (#{state})>"
|
342
|
+
else
|
343
|
+
"#<Fiber:#{object_id} #{location} (#{state})>"
|
344
|
+
end
|
339
345
|
end
|
340
346
|
alias_method :to_s, :inspect
|
341
347
|
|
@@ -19,22 +19,13 @@ class ::Socket
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def recv(maxlen, flags = 0, outbuf = nil)
|
22
|
-
Thread.current.backend.recv(self,
|
23
|
-
# outbuf ||= +''
|
24
|
-
# loop do
|
25
|
-
# result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
|
26
|
-
# case result
|
27
|
-
# when nil then raise IOError
|
28
|
-
# when :wait_readable then Thread.current.backend.wait_io(self, false)
|
29
|
-
# else
|
30
|
-
# return result
|
31
|
-
# end
|
32
|
-
# end
|
22
|
+
Thread.current.backend.recv(self, outbuf || +'', maxlen)
|
33
23
|
end
|
34
24
|
|
35
25
|
def recv_loop(&block)
|
36
26
|
Thread.current.backend.recv_loop(self, &block)
|
37
27
|
end
|
28
|
+
alias_method :read_loop, :recv_loop
|
38
29
|
|
39
30
|
def recvfrom(maxlen, flags = 0)
|
40
31
|
@read_buffer ||= +''
|
@@ -53,8 +44,12 @@ class ::Socket
|
|
53
44
|
Thread.current.backend.send(self, mesg)
|
54
45
|
end
|
55
46
|
|
56
|
-
def write(str)
|
57
|
-
|
47
|
+
def write(str, *args)
|
48
|
+
if args.empty?
|
49
|
+
Thread.current.backend.send(self, str)
|
50
|
+
else
|
51
|
+
Thread.current.backend.send(self, str + args.join)
|
52
|
+
end
|
58
53
|
end
|
59
54
|
alias_method :<<, :write
|
60
55
|
|
@@ -145,13 +140,18 @@ class ::TCPSocket
|
|
145
140
|
def recv_loop(&block)
|
146
141
|
Thread.current.backend.recv_loop(self, &block)
|
147
142
|
end
|
143
|
+
alias_method :read_loop, :recv_loop
|
148
144
|
|
149
145
|
def send(mesg, flags = 0)
|
150
146
|
Thread.current.backend.send(self, mesg)
|
151
147
|
end
|
152
148
|
|
153
|
-
def write(str)
|
154
|
-
|
149
|
+
def write(str, *args)
|
150
|
+
if args.empty?
|
151
|
+
Thread.current.backend.send(self, str)
|
152
|
+
else
|
153
|
+
Thread.current.backend.send(self, str + args.join)
|
154
|
+
end
|
155
155
|
end
|
156
156
|
alias_method :<<, :write
|
157
157
|
|
@@ -200,3 +200,62 @@ class ::TCPServer
|
|
200
200
|
@io.close
|
201
201
|
end
|
202
202
|
end
|
203
|
+
|
204
|
+
class ::UNIXServer
|
205
|
+
alias_method :orig_accept, :accept
|
206
|
+
def accept
|
207
|
+
Thread.current.backend.accept(self)
|
208
|
+
end
|
209
|
+
|
210
|
+
def accept_loop(&block)
|
211
|
+
Thread.current.backend.accept_loop(self, &block)
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
class ::UNIXSocket
|
218
|
+
def recv(maxlen, flags = 0, outbuf = nil)
|
219
|
+
Thread.current.backend.recv(self, buf || +'', maxlen)
|
220
|
+
end
|
221
|
+
|
222
|
+
def recv_loop(&block)
|
223
|
+
Thread.current.backend.recv_loop(self, &block)
|
224
|
+
end
|
225
|
+
alias_method :read_loop, :recv_loop
|
226
|
+
|
227
|
+
def send(mesg, flags = 0)
|
228
|
+
Thread.current.backend.send(self, mesg)
|
229
|
+
end
|
230
|
+
|
231
|
+
def write(str, *args)
|
232
|
+
if args.empty?
|
233
|
+
Thread.current.backend.send(self, str)
|
234
|
+
else
|
235
|
+
Thread.current.backend.send(self, str + args.join)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
alias_method :<<, :write
|
239
|
+
|
240
|
+
def readpartial(maxlen, str = nil)
|
241
|
+
@read_buffer ||= +''
|
242
|
+
result = Thread.current.backend.recv(self, @read_buffer, maxlen)
|
243
|
+
raise EOFError unless result
|
244
|
+
|
245
|
+
if str
|
246
|
+
str << @read_buffer
|
247
|
+
else
|
248
|
+
str = @read_buffer
|
249
|
+
end
|
250
|
+
@read_buffer = +''
|
251
|
+
str
|
252
|
+
end
|
253
|
+
|
254
|
+
def read_nonblock(len, str = nil, exception: true)
|
255
|
+
@io.read_nonblock(len, str, exception: exception)
|
256
|
+
end
|
257
|
+
|
258
|
+
def write_nonblock(buf, exception: true)
|
259
|
+
@io.write_nonblock(buf, exception: exception)
|
260
|
+
end
|
261
|
+
end
|
data/lib/polyphony/version.rb
CHANGED
data/test/test_backend.rb
CHANGED
@@ -136,4 +136,52 @@ class BackendTest < MiniTest::Test
|
|
136
136
|
f.await # TODO: check why this test sometimes segfaults if we don't a<wait fiber
|
137
137
|
assert_in_range 4..6, i
|
138
138
|
end
|
139
|
+
|
140
|
+
class MyTimeoutException < Exception
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_timeout
|
144
|
+
buffer = []
|
145
|
+
assert_raises(Polyphony::TimeoutException) do
|
146
|
+
@backend.timeout(0.01, Polyphony::TimeoutException) do
|
147
|
+
buffer << 1
|
148
|
+
sleep 0.02
|
149
|
+
buffer << 2
|
150
|
+
end
|
151
|
+
end
|
152
|
+
assert_equal [1], buffer
|
153
|
+
|
154
|
+
buffer = []
|
155
|
+
assert_raises(MyTimeoutException) do
|
156
|
+
@backend.timeout(0.01, MyTimeoutException) do
|
157
|
+
buffer << 1
|
158
|
+
sleep 1
|
159
|
+
buffer << 2
|
160
|
+
end
|
161
|
+
end
|
162
|
+
assert_equal [1], buffer
|
163
|
+
|
164
|
+
buffer = []
|
165
|
+
result = @backend.timeout(0.01, nil, 42) do
|
166
|
+
buffer << 1
|
167
|
+
sleep 1
|
168
|
+
buffer << 2
|
169
|
+
end
|
170
|
+
assert_equal 42, result
|
171
|
+
assert_equal [1], buffer
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_nested_timeout
|
175
|
+
buffer = []
|
176
|
+
assert_raises(MyTimeoutException) do
|
177
|
+
@backend.timeout(0.01, MyTimeoutException) do
|
178
|
+
@backend.timeout(0.02, nil) do
|
179
|
+
buffer << 1
|
180
|
+
sleep 1
|
181
|
+
buffer << 2
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
assert_equal [1], buffer
|
186
|
+
end
|
139
187
|
end
|
data/test/test_fiber.rb
CHANGED
@@ -565,14 +565,14 @@ class FiberTest < MiniTest::Test
|
|
565
565
|
end
|
566
566
|
|
567
567
|
def test_inspect
|
568
|
-
expected = format('#<Fiber:%s (root) (running)>', Fiber.current.object_id)
|
568
|
+
expected = format('#<Fiber main:%s (root) (running)>', Fiber.current.object_id)
|
569
569
|
assert_equal expected, Fiber.current.inspect
|
570
570
|
|
571
571
|
spin_line_no = __LINE__ + 1
|
572
|
-
f = spin { :foo }
|
572
|
+
f = spin(:baz) { :foo }
|
573
573
|
|
574
574
|
expected = format(
|
575
|
-
'#<Fiber:%s %s:%d:in `test_inspect\' (runnable)>',
|
575
|
+
'#<Fiber baz:%s %s:%d:in `test_inspect\' (runnable)>',
|
576
576
|
f.object_id,
|
577
577
|
__FILE__,
|
578
578
|
spin_line_no
|
@@ -581,7 +581,7 @@ class FiberTest < MiniTest::Test
|
|
581
581
|
|
582
582
|
f.await
|
583
583
|
expected = format(
|
584
|
-
'#<Fiber:%s %s:%d:in `test_inspect\' (dead)>',
|
584
|
+
'#<Fiber baz:%s %s:%d:in `test_inspect\' (dead)>',
|
585
585
|
f.object_id,
|
586
586
|
__FILE__,
|
587
587
|
spin_line_no
|
@@ -738,6 +738,35 @@ class MailboxTest < MiniTest::Test
|
|
738
738
|
f&.stop
|
739
739
|
end
|
740
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
|
+
|
741
770
|
def test_that_multiple_messages_sent_at_once_arrive_in_order
|
742
771
|
msgs = []
|
743
772
|
f = spin { loop { msgs << receive } }
|
data/test/test_global_api.rb
CHANGED
@@ -150,6 +150,28 @@ class MoveOnAfterTest < MiniTest::Test
|
|
150
150
|
assert t1 - t0 < 0.1
|
151
151
|
assert_equal 'foo', v
|
152
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
|
153
175
|
end
|
154
176
|
|
155
177
|
class CancelAfterTest < MiniTest::Test
|
@@ -188,7 +210,7 @@ class CancelAfterTest < MiniTest::Test
|
|
188
210
|
sleep 0.007
|
189
211
|
end
|
190
212
|
t1 = Time.now
|
191
|
-
assert_in_range 0.014..0.
|
213
|
+
assert_in_range 0.014..0.024, t1 - t0
|
192
214
|
end
|
193
215
|
|
194
216
|
class CustomException < Exception
|
@@ -275,6 +297,55 @@ class SpinLoopTest < MiniTest::Test
|
|
275
297
|
f.stop
|
276
298
|
assert_in_range 1..3, counter
|
277
299
|
end
|
300
|
+
|
301
|
+
def test_spin_loop_with_interval
|
302
|
+
buffer = []
|
303
|
+
counter = 0
|
304
|
+
t0 = Time.now
|
305
|
+
f = spin_loop(interval: 0.01) { buffer << (counter += 1) }
|
306
|
+
sleep 0.02
|
307
|
+
f.stop
|
308
|
+
assert_in_range 1..3, counter
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class SpinScopeTest < MiniTest::Test
|
313
|
+
def test_spin_scope
|
314
|
+
queue = Queue.new
|
315
|
+
buffer = {}
|
316
|
+
spin do
|
317
|
+
queue << 1
|
318
|
+
snooze
|
319
|
+
queue << 2
|
320
|
+
end
|
321
|
+
f = nil
|
322
|
+
result = spin_scope do
|
323
|
+
f = Fiber.current
|
324
|
+
spin { buffer[:a] = queue.shift }
|
325
|
+
spin { buffer[:b] = queue.shift }
|
326
|
+
:foo
|
327
|
+
end
|
328
|
+
assert_equal :foo, result
|
329
|
+
assert_kind_of Fiber, f
|
330
|
+
assert_equal :dead, f.state
|
331
|
+
assert_equal ({a: 1, b: 2}), buffer
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_spin_scope_with_exception
|
335
|
+
queue = Queue.new
|
336
|
+
buffer = []
|
337
|
+
spin do
|
338
|
+
spin_scope do
|
339
|
+
spin { buffer << queue.shift }
|
340
|
+
spin { raise 'foobar' }
|
341
|
+
end
|
342
|
+
rescue => e
|
343
|
+
buffer << e.message
|
344
|
+
end
|
345
|
+
6.times { snooze }
|
346
|
+
assert_equal 0, Fiber.current.children.size
|
347
|
+
assert_equal ['foobar'], buffer
|
348
|
+
end
|
278
349
|
end
|
279
350
|
|
280
351
|
class ThrottledLoopTest < MiniTest::Test
|