polyphony 0.66 → 0.70
Sign up to get free protection for your applications and to get access to all the features.
- 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
|