polyphony 0.66 → 0.70
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 +23 -1
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -63
- data/bin/pdbg +112 -0
- data/examples/core/await.rb +9 -1
- data/ext/polyphony/backend_common.c +32 -1
- data/ext/polyphony/backend_common.h +4 -1
- data/ext/polyphony/backend_io_uring.c +17 -5
- data/ext/polyphony/backend_libev.c +16 -0
- data/ext/polyphony/fiber.c +20 -0
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +5 -2
- data/ext/polyphony/runqueue.c +4 -0
- data/ext/polyphony/runqueue.h +1 -0
- data/ext/polyphony/runqueue_ring_buffer.c +25 -14
- data/ext/polyphony/runqueue_ring_buffer.h +2 -0
- data/ext/polyphony/thread.c +2 -8
- data/lib/polyphony.rb +6 -0
- data/lib/polyphony/debugger.rb +225 -0
- data/lib/polyphony/extensions/debug.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +66 -69
- data/lib/polyphony/extensions/io.rb +1 -3
- data/lib/polyphony/extensions/openssl.rb +63 -1
- data/lib/polyphony/extensions/socket.rb +3 -3
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +4 -5
- data/test/stress.rb +6 -2
- data/test/test_fiber.rb +30 -11
- data/test/test_process_supervision.rb +38 -9
- data/test/test_supervise.rb +183 -100
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +2 -2
- metadata +4 -2
@@ -7,6 +7,10 @@ require_relative '../core/exceptions'
|
|
7
7
|
module Polyphony
|
8
8
|
# Fiber control API
|
9
9
|
module FiberControl
|
10
|
+
def monitor_mailbox
|
11
|
+
@monitor_mailbox ||= Polyphony::Queue.new
|
12
|
+
end
|
13
|
+
|
10
14
|
def interrupt(value = nil)
|
11
15
|
return if @running == false
|
12
16
|
|
@@ -23,6 +27,7 @@ module Polyphony
|
|
23
27
|
end
|
24
28
|
|
25
29
|
fiber = parent.spin(@tag, @caller, &@block)
|
30
|
+
@monitors&.each_key { |f| fiber.monitor(f) }
|
26
31
|
fiber.schedule(value) unless value.nil?
|
27
32
|
fiber
|
28
33
|
end
|
@@ -75,46 +80,34 @@ module Polyphony
|
|
75
80
|
|
76
81
|
# Fiber supervision
|
77
82
|
module FiberSupervision
|
78
|
-
def supervise(opts
|
79
|
-
|
80
|
-
|
81
|
-
|
83
|
+
def supervise(*fibers, **opts, &block)
|
84
|
+
block ||= supervise_opts_to_block(opts)
|
85
|
+
|
86
|
+
fibers.each do |f|
|
87
|
+
f.attach_to(self) unless f.parent == self
|
88
|
+
f.monitor(self)
|
82
89
|
end
|
90
|
+
|
91
|
+
mailbox = monitor_mailbox
|
92
|
+
|
83
93
|
while true
|
84
|
-
|
94
|
+
(fiber, result) = mailbox.shift
|
95
|
+
block&.call(fiber, result)
|
85
96
|
end
|
86
|
-
rescue Polyphony::MoveOn
|
87
|
-
# generated in #supervise_perform to stop supervisor
|
88
|
-
ensure
|
89
|
-
@on_child_done = nil
|
90
97
|
end
|
91
98
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
restart_fiber(fiber, opts)
|
96
|
-
elsif Fiber.current.children.empty?
|
97
|
-
Fiber.current.stop
|
98
|
-
end
|
99
|
-
rescue Polyphony::Restart
|
100
|
-
restart_all_children
|
101
|
-
rescue Exception => e
|
102
|
-
Kernel.raise e if e.source_fiber.nil? || e.source_fiber == self
|
99
|
+
def supervise_opts_to_block(opts)
|
100
|
+
block = opts[:on_done] || opts[:on_error]
|
101
|
+
return nil unless block || opts[:restart]
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
Fiber.current.stop
|
108
|
-
end
|
109
|
-
end
|
103
|
+
error_only = !!opts[:on_error]
|
104
|
+
restart_always = opts[:restart] == :always
|
105
|
+
restart_on_error = opts[:restart] == :on_error
|
110
106
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
fiber.restart
|
116
|
-
when :one_for_all
|
117
|
-
@children.keys.each(&:restart)
|
107
|
+
->(f, r) do
|
108
|
+
is_error = r.is_a?(Exception)
|
109
|
+
block.(f, r) if block && (!error_only || is_error)
|
110
|
+
f.restart if restart_always || (restart_on_error && is_error)
|
118
111
|
end
|
119
112
|
end
|
120
113
|
end
|
@@ -124,23 +117,24 @@ module Polyphony
|
|
124
117
|
def await(*fibers)
|
125
118
|
return [] if fibers.empty?
|
126
119
|
|
127
|
-
|
120
|
+
current_fiber = self.current
|
121
|
+
mailbox = current_fiber.monitor_mailbox
|
128
122
|
results = {}
|
129
123
|
fibers.each do |f|
|
130
124
|
results[f] = nil
|
131
125
|
if f.dead?
|
132
126
|
# fiber already terminated, so queue message
|
133
|
-
|
127
|
+
mailbox << [f, f.result]
|
134
128
|
else
|
135
|
-
f.monitor
|
129
|
+
f.monitor(current_fiber)
|
136
130
|
end
|
137
131
|
end
|
138
132
|
exception = nil
|
139
133
|
while !fibers.empty?
|
140
|
-
(fiber, result) =
|
134
|
+
(fiber, result) = mailbox.shift
|
141
135
|
next unless fibers.include?(fiber)
|
142
|
-
|
143
136
|
fibers.delete(fiber)
|
137
|
+
current_fiber.remove_child(fiber) if fiber.parent == current_fiber
|
144
138
|
if result.is_a?(Exception)
|
145
139
|
exception ||= result
|
146
140
|
fibers.each { |f| f.terminate }
|
@@ -148,16 +142,16 @@ module Polyphony
|
|
148
142
|
results[fiber] = result
|
149
143
|
end
|
150
144
|
end
|
151
|
-
results.values
|
152
|
-
ensure
|
153
|
-
Fiber.current.message_on_child_termination = false
|
154
145
|
raise exception if exception
|
146
|
+
results.values
|
155
147
|
end
|
156
148
|
alias_method :join, :await
|
157
149
|
|
158
150
|
def select(*fibers)
|
159
151
|
return nil if fibers.empty?
|
160
152
|
|
153
|
+
current_fiber = self.current
|
154
|
+
mailbox = current_fiber.monitor_mailbox
|
161
155
|
fibers.each do |f|
|
162
156
|
if f.dead?
|
163
157
|
result = f.result
|
@@ -165,21 +159,18 @@ module Polyphony
|
|
165
159
|
end
|
166
160
|
end
|
167
161
|
|
168
|
-
|
169
|
-
fibers.each { |f| f.monitor }
|
162
|
+
fibers.each { |f| f.monitor(current_fiber) }
|
170
163
|
while true
|
171
|
-
(fiber, result) =
|
164
|
+
(fiber, result) = mailbox.shift
|
172
165
|
next unless fibers.include?(fiber)
|
173
166
|
|
174
|
-
fibers.each { |f| f.unmonitor }
|
167
|
+
fibers.each { |f| f.unmonitor(current_fiber) }
|
175
168
|
if result.is_a?(Exception)
|
176
169
|
raise result
|
177
170
|
else
|
178
171
|
return [fiber, result]
|
179
172
|
end
|
180
173
|
end
|
181
|
-
ensure
|
182
|
-
Fiber.current.message_on_child_termination = false
|
183
174
|
end
|
184
175
|
|
185
176
|
# Creates and schedules with priority an out-of-band fiber that runs the
|
@@ -219,14 +210,6 @@ module Polyphony
|
|
219
210
|
f
|
220
211
|
end
|
221
212
|
|
222
|
-
def child_done(child_fiber, result)
|
223
|
-
@children.delete(child_fiber)
|
224
|
-
|
225
|
-
if result.is_a?(Exception) && !@message_on_child_termination
|
226
|
-
schedule_with_priority(result)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
213
|
def terminate_all_children(graceful = false)
|
231
214
|
return unless @children
|
232
215
|
|
@@ -240,13 +223,15 @@ module Polyphony
|
|
240
223
|
def await_all_children
|
241
224
|
return unless @children && !@children.empty?
|
242
225
|
|
243
|
-
Fiber.await(*@children.keys)
|
226
|
+
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
244
227
|
end
|
245
228
|
|
246
229
|
def shutdown_all_children(graceful = false)
|
247
230
|
return unless @children
|
248
231
|
|
249
232
|
@children.keys.each do |c|
|
233
|
+
next if c.dead?
|
234
|
+
|
250
235
|
c.terminate(graceful)
|
251
236
|
c.await
|
252
237
|
end
|
@@ -258,10 +243,17 @@ module Polyphony
|
|
258
243
|
@parent.add_child(self)
|
259
244
|
end
|
260
245
|
|
261
|
-
def
|
246
|
+
def attach_to(fiber)
|
262
247
|
@parent.remove_child(self)
|
263
|
-
@parent =
|
264
|
-
|
248
|
+
@parent = fiber
|
249
|
+
fiber.add_child(self)
|
250
|
+
end
|
251
|
+
|
252
|
+
def attach_and_monitor(fiber)
|
253
|
+
@parent.remove_child(self)
|
254
|
+
@parent = fiber
|
255
|
+
fiber.add_child(self)
|
256
|
+
monitor(fiber)
|
265
257
|
end
|
266
258
|
end
|
267
259
|
|
@@ -323,9 +315,10 @@ module Polyphony
|
|
323
315
|
Thread.backend.trace(:fiber_terminate, self, result)
|
324
316
|
@result = result
|
325
317
|
|
326
|
-
|
318
|
+
inform_monitors(result, uncaught_exception)
|
327
319
|
@running = false
|
328
320
|
ensure
|
321
|
+
@parent&.remove_child(self)
|
329
322
|
# Prevent fiber from being resumed after terminating
|
330
323
|
@thread.fiber_unschedule(self)
|
331
324
|
Thread.current.switch_fiber
|
@@ -341,24 +334,28 @@ module Polyphony
|
|
341
334
|
[e, true]
|
342
335
|
end
|
343
336
|
|
344
|
-
def
|
337
|
+
def inform_monitors(result, uncaught_exception)
|
345
338
|
if @monitors
|
346
339
|
msg = [self, result]
|
347
|
-
@monitors.
|
340
|
+
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
348
341
|
end
|
349
342
|
|
350
|
-
@parent
|
343
|
+
if uncaught_exception && @parent
|
344
|
+
parent_is_monitor = @monitors&.has_key?(@parent)
|
345
|
+
@parent.schedule_with_priority(result) unless parent_is_monitor
|
346
|
+
end
|
351
347
|
end
|
352
348
|
|
353
|
-
|
349
|
+
def monitor(fiber)
|
350
|
+
(@monitors ||= {})[fiber] = true
|
351
|
+
end
|
354
352
|
|
355
|
-
def
|
356
|
-
@monitors ||= []
|
357
|
-
@monitors << Fiber.current
|
353
|
+
def unmonitor(fiber)
|
354
|
+
(@monitors ||= []).delete(fiber)
|
358
355
|
end
|
359
356
|
|
360
|
-
def
|
361
|
-
@monitors
|
357
|
+
def monitors
|
358
|
+
@monitors&.keys || []
|
362
359
|
end
|
363
360
|
|
364
361
|
def dead?
|
@@ -76,7 +76,7 @@ end
|
|
76
76
|
|
77
77
|
# IO instance method patches
|
78
78
|
class ::IO
|
79
|
-
def
|
79
|
+
def __parser_read_method__
|
80
80
|
:backend_read
|
81
81
|
end
|
82
82
|
|
@@ -160,8 +160,6 @@ class ::IO
|
|
160
160
|
return @read_buffer.slice!(0, idx + sep_size) if idx
|
161
161
|
|
162
162
|
result = readpartial(8192, @read_buffer, -1)
|
163
|
-
|
164
|
-
#Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
|
165
163
|
return nil unless result
|
166
164
|
end
|
167
165
|
rescue EOFError
|
@@ -5,7 +5,7 @@ require_relative './socket'
|
|
5
5
|
|
6
6
|
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
7
7
|
class ::OpenSSL::SSL::SSLSocket
|
8
|
-
def
|
8
|
+
def __parser_read_method__
|
9
9
|
:readpartial
|
10
10
|
end
|
11
11
|
|
@@ -114,6 +114,68 @@ end
|
|
114
114
|
|
115
115
|
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
116
116
|
class ::OpenSSL::SSL::SSLServer
|
117
|
+
attr_reader :ctx
|
118
|
+
|
119
|
+
alias_method :orig_accept, :accept
|
120
|
+
def accept
|
121
|
+
# when @ctx.servername_cb is set, we use a worker thread to run the
|
122
|
+
# ssl.accept call. We need to do this because:
|
123
|
+
# - We cannot switch fibers inside of the servername_cb proc (see
|
124
|
+
# https://github.com/ruby/openssl/issues/415)
|
125
|
+
# - We don't want to stop the world while we're busy provisioning an ACME
|
126
|
+
# certificate
|
127
|
+
if @use_accept_worker.nil?
|
128
|
+
if (@use_accept_worker = use_accept_worker_thread?)
|
129
|
+
start_accept_worker_thread
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
sock, = @svr.accept
|
134
|
+
begin
|
135
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
|
136
|
+
ssl.sync_close = true
|
137
|
+
if @use_accept_worker
|
138
|
+
@accept_worker_fiber << [ssl, Fiber.current]
|
139
|
+
receive
|
140
|
+
else
|
141
|
+
ssl.accept
|
142
|
+
end
|
143
|
+
ssl
|
144
|
+
rescue Exception => ex
|
145
|
+
if ssl
|
146
|
+
ssl.close
|
147
|
+
else
|
148
|
+
sock.close
|
149
|
+
end
|
150
|
+
raise ex
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def start_accept_worker_thread
|
155
|
+
fiber = Fiber.current
|
156
|
+
@accept_worker_thread = Thread.new do
|
157
|
+
fiber << Fiber.current
|
158
|
+
loop do
|
159
|
+
socket, peer = receive
|
160
|
+
socket.accept
|
161
|
+
peer << socket
|
162
|
+
rescue => e
|
163
|
+
peer.schedule(e) if fiber
|
164
|
+
end
|
165
|
+
end
|
166
|
+
@accept_worker_fiber = receive
|
167
|
+
end
|
168
|
+
|
169
|
+
def use_accept_worker_thread?
|
170
|
+
!!@ctx.servername_cb
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :orig_close, :close
|
174
|
+
def close
|
175
|
+
@accept_worker_thread&.kill
|
176
|
+
orig_close
|
177
|
+
end
|
178
|
+
|
117
179
|
def accept_loop(ignore_errors = true)
|
118
180
|
loop do
|
119
181
|
yield accept
|
@@ -6,7 +6,7 @@ require_relative './io'
|
|
6
6
|
require_relative '../core/thread_pool'
|
7
7
|
|
8
8
|
class BasicSocket
|
9
|
-
def
|
9
|
+
def __parser_read_method__
|
10
10
|
:backend_recv
|
11
11
|
end
|
12
12
|
end
|
@@ -206,7 +206,7 @@ class ::TCPSocket
|
|
206
206
|
# Polyphony.backend_send(self, mesg, 0)
|
207
207
|
# end
|
208
208
|
|
209
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
209
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
210
210
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
211
211
|
raise EOFError if !result && raise_on_eof
|
212
212
|
result
|
@@ -299,7 +299,7 @@ class ::UNIXSocket
|
|
299
299
|
Polyphony.backend_send(self, mesg, 0)
|
300
300
|
end
|
301
301
|
|
302
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
302
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
303
303
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
304
304
|
raise EOFError if !result && raise_on_eof
|
305
305
|
result
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -46,12 +46,7 @@ end
|
|
46
46
|
class MiniTest::Test
|
47
47
|
def setup
|
48
48
|
# trace "* setup #{self.name}"
|
49
|
-
if Fiber.current.children.size > 0
|
50
|
-
puts "Children left: #{Fiber.current.children.inspect}"
|
51
|
-
exit!
|
52
|
-
end
|
53
49
|
Fiber.current.setup_main_fiber
|
54
|
-
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
55
50
|
Thread.current.backend.finalize
|
56
51
|
Thread.current.backend = Polyphony::Backend.new
|
57
52
|
sleep 0.001
|
@@ -60,6 +55,10 @@ class MiniTest::Test
|
|
60
55
|
def teardown
|
61
56
|
# trace "* teardown #{self.name}"
|
62
57
|
Fiber.current.shutdown_all_children
|
58
|
+
if Fiber.current.children.size > 0
|
59
|
+
puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
|
60
|
+
exit!
|
61
|
+
end
|
63
62
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
64
63
|
rescue => e
|
65
64
|
puts e
|
data/test/stress.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
count = ARGV[0] ? ARGV[0].to_i : 100
|
4
|
+
test_name = ARGV[1]
|
4
5
|
|
5
|
-
|
6
|
+
$test_cmd = +'ruby test/run.rb'
|
7
|
+
if test_name
|
8
|
+
$test_cmd << " --name #{test_name}"
|
9
|
+
end
|
6
10
|
|
7
11
|
def run_test(count)
|
8
12
|
puts "#{count}: running tests..."
|
9
13
|
# sleep 1
|
10
|
-
system(
|
14
|
+
system($test_cmd)
|
11
15
|
puts
|
12
16
|
|
13
17
|
return if $?.exitstatus == 0
|
data/test/test_fiber.rb
CHANGED
@@ -46,7 +46,7 @@ class FiberTest < MiniTest::Test
|
|
46
46
|
def test_await_dead_children
|
47
47
|
f1 = spin { :foo }
|
48
48
|
f2 = spin { :bar }
|
49
|
-
|
49
|
+
4.times { snooze }
|
50
50
|
|
51
51
|
assert_equal [:foo, :bar], Fiber.await(f1, f2)
|
52
52
|
end
|
@@ -67,10 +67,10 @@ class FiberTest < MiniTest::Test
|
|
67
67
|
}
|
68
68
|
Fiber.await(f2, f3)
|
69
69
|
assert_equal [:foo, :bar, :baz], buffer
|
70
|
-
assert_equal
|
70
|
+
assert_equal [], Fiber.current.children
|
71
71
|
end
|
72
72
|
|
73
|
-
def test_await_from_multiple_fibers_with_interruption
|
73
|
+
def test_await_from_multiple_fibers_with_interruption
|
74
74
|
buffer = []
|
75
75
|
f1 = spin {
|
76
76
|
sleep 0.02
|
@@ -563,10 +563,10 @@ class FiberTest < MiniTest::Test
|
|
563
563
|
end
|
564
564
|
|
565
565
|
snooze
|
566
|
-
child.monitor
|
566
|
+
child.monitor(Fiber.current)
|
567
567
|
spin { child << :foo }
|
568
568
|
|
569
|
-
msg =
|
569
|
+
msg = Fiber.current.monitor_mailbox.shift
|
570
570
|
assert_equal [child, :foo], msg
|
571
571
|
end
|
572
572
|
|
@@ -578,14 +578,14 @@ class FiberTest < MiniTest::Test
|
|
578
578
|
end
|
579
579
|
|
580
580
|
snooze
|
581
|
-
child.monitor
|
581
|
+
child.monitor(Fiber.current)
|
582
582
|
spin { child << :foo }
|
583
583
|
snooze
|
584
584
|
|
585
|
-
child.unmonitor
|
585
|
+
child.unmonitor(Fiber.current)
|
586
586
|
|
587
|
-
Fiber.current << :bar
|
588
|
-
msg =
|
587
|
+
Fiber.current.monitor_mailbox << :bar
|
588
|
+
msg = Fiber.current.monitor_mailbox.shift
|
589
589
|
assert_equal :bar, msg
|
590
590
|
end
|
591
591
|
|
@@ -786,7 +786,7 @@ class FiberTest < MiniTest::Test
|
|
786
786
|
], buf
|
787
787
|
end
|
788
788
|
|
789
|
-
def
|
789
|
+
def test_attach_to
|
790
790
|
buf = []
|
791
791
|
child = nil
|
792
792
|
parent = spin(:parent) do
|
@@ -804,7 +804,7 @@ class FiberTest < MiniTest::Test
|
|
804
804
|
|
805
805
|
snooze
|
806
806
|
assert_equal parent, child.parent
|
807
|
-
child.
|
807
|
+
child.attach_to(new_parent)
|
808
808
|
assert_equal new_parent, child.parent
|
809
809
|
parent.await
|
810
810
|
|
@@ -1218,4 +1218,23 @@ class GracefulTerminationTest < MiniTest::Test
|
|
1218
1218
|
|
1219
1219
|
assert_equal [1, 2], buffer
|
1220
1220
|
end
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
class DebugTest < MiniTest::Test
|
1224
|
+
def test_parking
|
1225
|
+
buf = []
|
1226
|
+
f = spin do
|
1227
|
+
3.times { |i| snooze; buf << i }
|
1228
|
+
end
|
1229
|
+
assert_nil f.__parked__?
|
1230
|
+
f.__park__
|
1231
|
+
assert_equal true, f.__parked__?
|
1232
|
+
10.times { snooze }
|
1233
|
+
assert_equal [], buf
|
1234
|
+
|
1235
|
+
f.__unpark__
|
1236
|
+
assert_nil f.__parked__?
|
1237
|
+
10.times { snooze }
|
1238
|
+
assert_equal [0, 1, 2], buf
|
1239
|
+
end
|
1221
1240
|
end
|