polyphony 0.46.0 → 0.47.3
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 +24 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +54 -23
- data/bin/test +4 -0
- data/examples/core/enumerable.rb +64 -0
- data/examples/performance/fiber_resume.rb +43 -0
- data/examples/performance/fiber_transfer.rb +13 -4
- 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 +9 -19
- 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 +20 -0
- data/ext/polyphony/backend_io_uring.c +127 -16
- 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 +102 -0
- data/ext/polyphony/fiber.c +11 -7
- data/ext/polyphony/polyphony.c +3 -0
- data/ext/polyphony/polyphony.h +7 -7
- data/ext/polyphony/queue.c +99 -34
- data/ext/polyphony/thread.c +1 -3
- data/lib/polyphony/core/exceptions.rb +0 -4
- data/lib/polyphony/core/global_api.rb +49 -31
- data/lib/polyphony/extensions/core.rb +9 -15
- data/lib/polyphony/extensions/fiber.rb +8 -2
- data/lib/polyphony/extensions/openssl.rb +6 -0
- data/lib/polyphony/extensions/socket.rb +18 -4
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/stress.rb +1 -1
- data/test/test_backend.rb +59 -0
- data/test/test_fiber.rb +33 -4
- data/test/test_global_api.rb +85 -1
- data/test/test_queue.rb +117 -0
- data/test/test_signal.rb +18 -0
- data/test/test_socket.rb +2 -2
- metadata +8 -2
@@ -12,7 +12,7 @@ class ::Exception
|
|
12
12
|
attr_accessor :__disable_sanitized_backtrace__
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :source_fiber
|
15
|
+
attr_accessor :source_fiber, :__raising_fiber__
|
16
16
|
|
17
17
|
alias_method :orig_initialize, :initialize
|
18
18
|
def initialize(*args)
|
@@ -22,8 +22,8 @@ class ::Exception
|
|
22
22
|
|
23
23
|
alias_method :orig_backtrace, :backtrace
|
24
24
|
def backtrace
|
25
|
-
unless @
|
26
|
-
@
|
25
|
+
unless @backtrace_called
|
26
|
+
@backtrace_called = true
|
27
27
|
return orig_backtrace
|
28
28
|
end
|
29
29
|
|
@@ -31,12 +31,10 @@ class ::Exception
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def sanitized_backtrace
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
sanitize(orig_backtrace)
|
39
|
-
end
|
34
|
+
return sanitize(orig_backtrace) unless @__raising_fiber__
|
35
|
+
|
36
|
+
backtrace = orig_backtrace || []
|
37
|
+
sanitize(backtrace + @__raising_fiber__.caller)
|
40
38
|
end
|
41
39
|
|
42
40
|
POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
|
@@ -164,11 +162,7 @@ end
|
|
164
162
|
|
165
163
|
# Override Timeout to use cancel scope
|
166
164
|
module ::Timeout
|
167
|
-
def self.timeout(sec, klass =
|
168
|
-
cancel_after(sec, &block)
|
169
|
-
rescue Polyphony::Cancel => e
|
170
|
-
error = klass ? klass.new(message) : ::Timeout::Error.new
|
171
|
-
error.set_backtrace(e.backtrace)
|
172
|
-
raise error
|
165
|
+
def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
|
166
|
+
cancel_after(sec, with_exception: [klass, message], &block)
|
173
167
|
end
|
174
168
|
end
|
@@ -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
|
|
@@ -35,6 +35,7 @@ class ::Socket
|
|
35
35
|
def recv_loop(&block)
|
36
36
|
Thread.current.backend.recv_loop(self, &block)
|
37
37
|
end
|
38
|
+
alias_method :read_loop, :recv_loop
|
38
39
|
|
39
40
|
def recvfrom(maxlen, flags = 0)
|
40
41
|
@read_buffer ||= +''
|
@@ -53,8 +54,12 @@ class ::Socket
|
|
53
54
|
Thread.current.backend.send(self, mesg)
|
54
55
|
end
|
55
56
|
|
56
|
-
def write(str)
|
57
|
-
|
57
|
+
def write(str, *args)
|
58
|
+
if args.empty?
|
59
|
+
Thread.current.backend.send(self, str)
|
60
|
+
else
|
61
|
+
Thread.current.backend.send(self, str + args.join)
|
62
|
+
end
|
58
63
|
end
|
59
64
|
alias_method :<<, :write
|
60
65
|
|
@@ -145,13 +150,18 @@ class ::TCPSocket
|
|
145
150
|
def recv_loop(&block)
|
146
151
|
Thread.current.backend.recv_loop(self, &block)
|
147
152
|
end
|
153
|
+
alias_method :read_loop, :recv_loop
|
148
154
|
|
149
155
|
def send(mesg, flags = 0)
|
150
156
|
Thread.current.backend.send(self, mesg)
|
151
157
|
end
|
152
158
|
|
153
|
-
def write(str)
|
154
|
-
|
159
|
+
def write(str, *args)
|
160
|
+
if args.empty?
|
161
|
+
Thread.current.backend.send(self, str)
|
162
|
+
else
|
163
|
+
Thread.current.backend.send(self, str + args.join)
|
164
|
+
end
|
155
165
|
end
|
156
166
|
alias_method :<<, :write
|
157
167
|
|
@@ -191,6 +201,10 @@ class ::TCPServer
|
|
191
201
|
@io.accept
|
192
202
|
end
|
193
203
|
|
204
|
+
def accept_loop(&block)
|
205
|
+
Thread.current.backend.accept_loop(@io, &block)
|
206
|
+
end
|
207
|
+
|
194
208
|
alias_method :orig_close, :close
|
195
209
|
def close
|
196
210
|
@io.close
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/stress.rb
CHANGED
data/test/test_backend.rb
CHANGED
@@ -125,4 +125,63 @@ class BackendTest < MiniTest::Test
|
|
125
125
|
snooze
|
126
126
|
server&.close
|
127
127
|
end
|
128
|
+
|
129
|
+
def test_timer_loop
|
130
|
+
i = 0
|
131
|
+
f = spin do
|
132
|
+
@backend.timer_loop(0.01) { i += 1 }
|
133
|
+
end
|
134
|
+
@backend.sleep(0.05)
|
135
|
+
f.stop
|
136
|
+
f.await # TODO: check why this test sometimes segfaults if we don't a<wait fiber
|
137
|
+
assert_in_range 4..6, i
|
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
|
128
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
|
@@ -202,6 +224,19 @@ class CancelAfterTest < MiniTest::Test
|
|
202
224
|
end
|
203
225
|
end
|
204
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
|
+
|
205
240
|
begin
|
206
241
|
e = nil
|
207
242
|
cancel_after(0.01, with_exception: 'foo') do
|
@@ -262,6 +297,55 @@ class SpinLoopTest < MiniTest::Test
|
|
262
297
|
f.stop
|
263
298
|
assert_in_range 1..3, counter
|
264
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
|
265
349
|
end
|
266
350
|
|
267
351
|
class ThrottledLoopTest < MiniTest::Test
|
data/test/test_queue.rb
CHANGED
@@ -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
|