polyphony 0.64 → 0.68
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -40
- data/bin/pdbg +112 -0
- data/examples/core/await.rb +9 -1
- data/ext/polyphony/backend_common.c +14 -1
- data/ext/polyphony/backend_common.h +3 -1
- data/ext/polyphony/backend_io_uring.c +85 -25
- data/ext/polyphony/backend_io_uring_context.c +42 -0
- data/ext/polyphony/backend_io_uring_context.h +6 -9
- data/ext/polyphony/backend_libev.c +85 -39
- data/ext/polyphony/fiber.c +20 -0
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +5 -2
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/runqueue.c +7 -3
- data/ext/polyphony/runqueue.h +4 -3
- 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 +64 -71
- data/lib/polyphony/extensions/io.rb +4 -2
- data/lib/polyphony/extensions/openssl.rb +66 -0
- data/lib/polyphony/extensions/socket.rb +8 -2
- data/lib/polyphony/net.rb +1 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +6 -5
- data/test/stress.rb +6 -2
- data/test/test_backend.rb +13 -4
- data/test/test_fiber.rb +35 -11
- data/test/test_global_api.rb +9 -4
- data/test/test_io.rb +2 -0
- data/test/test_socket.rb +14 -11
- data/test/test_supervise.rb +24 -24
- data/test/test_thread.rb +3 -0
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +2 -2
- data/test/test_timer.rb +5 -3
- metadata +5 -3
@@ -29,4 +29,6 @@ void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE
|
|
29
29
|
void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
30
|
int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
|
31
31
|
|
32
|
+
void runqueue_ring_buffer_migrate(runqueue_ring_buffer *src, runqueue_ring_buffer *dest, VALUE fiber);
|
33
|
+
|
32
34
|
#endif /* RUNQUEUE_RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -22,14 +22,12 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
|
|
22
22
|
return self;
|
23
23
|
}
|
24
24
|
|
25
|
-
|
25
|
+
inline void Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
26
26
|
schedule_fiber(self, fiber, value, 0);
|
27
|
-
return self;
|
28
27
|
}
|
29
28
|
|
30
|
-
|
29
|
+
inline void Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
|
31
30
|
schedule_fiber(self, fiber, value, 1);
|
32
|
-
return self;
|
33
31
|
}
|
34
32
|
|
35
33
|
VALUE Thread_switch_fiber(VALUE self) {
|
@@ -61,10 +59,6 @@ VALUE Thread_class_backend(VALUE _self) {
|
|
61
59
|
void Init_Thread() {
|
62
60
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
63
61
|
rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
|
64
|
-
|
65
|
-
rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
|
66
|
-
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
67
|
-
Thread_schedule_fiber_with_priority, 2);
|
68
62
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
69
63
|
rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
|
70
64
|
|
data/lib/polyphony.rb
CHANGED
@@ -121,3 +121,9 @@ end
|
|
121
121
|
|
122
122
|
Polyphony.install_terminating_signal_handlers
|
123
123
|
Polyphony.install_at_exit_handler
|
124
|
+
|
125
|
+
if (debug_socket_path = ENV['POLYPHONY_DEBUG_SOCKET_PATH'])
|
126
|
+
puts "Starting debug server on #{debug_socket_path}"
|
127
|
+
require 'polyphony/debugger'
|
128
|
+
Polyphony.start_debug_server(debug_socket_path)
|
129
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony/extensions/debug'
|
4
|
+
|
5
|
+
module Polyphony
|
6
|
+
TP_EVENTS = [
|
7
|
+
:line,
|
8
|
+
:call,
|
9
|
+
:return,
|
10
|
+
:b_call,
|
11
|
+
:b_return
|
12
|
+
]
|
13
|
+
|
14
|
+
def self.start_debug_server(socket_path)
|
15
|
+
server = DebugServer.new(socket_path)
|
16
|
+
controller = DebugController.new(server)
|
17
|
+
trace = TracePoint.new(*TP_EVENTS) { |tp| controller.handle_tp(trace, tp) }
|
18
|
+
trace.enable
|
19
|
+
|
20
|
+
at_exit do
|
21
|
+
Kernel.trace "program terminated"
|
22
|
+
trace.disable
|
23
|
+
server.stop
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class DebugController
|
28
|
+
def initialize(server)
|
29
|
+
@server = server
|
30
|
+
@server.wait_for_client
|
31
|
+
@state = { fibers: {} }
|
32
|
+
@control_fiber = Fiber.new { |f| control_loop(f) }
|
33
|
+
@control_fiber.transfer Fiber.current
|
34
|
+
end
|
35
|
+
|
36
|
+
def control_loop(source_fiber)
|
37
|
+
@peer = source_fiber
|
38
|
+
cmd = { cmd: :initial }
|
39
|
+
loop do
|
40
|
+
cmd = send(:"cmd_#{cmd[:cmd]}", cmd)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
POLYPHONY_LIB_DIR = File.expand_path('..', __dir__)
|
45
|
+
|
46
|
+
def get_next_trace_event
|
47
|
+
@peer.transfer.tap { |e| update_state(e) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_state(event)
|
51
|
+
trace update_state: event
|
52
|
+
@state[:fiber] = event[:fiber]
|
53
|
+
@state[:path] = event[:path]
|
54
|
+
@state[:lineno] = event[:lineno]
|
55
|
+
update_fiber_state(event)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_fiber_state(event)
|
59
|
+
fiber_state = @state[:fibers][event[:fiber]] ||= { stack: [] }
|
60
|
+
case event[:kind]
|
61
|
+
when :call, :c_call, :b_call
|
62
|
+
fiber_state[:stack] << event
|
63
|
+
when :return, :c_return, :b_return
|
64
|
+
fiber_state[:stack].pop
|
65
|
+
end
|
66
|
+
fiber_state[:binding] = event[:binding]
|
67
|
+
fiber_state[:path] = event[:path]
|
68
|
+
fiber_state[:lineno] = event[:lineno]
|
69
|
+
end
|
70
|
+
|
71
|
+
def state_presentation(state)
|
72
|
+
{
|
73
|
+
fiber: fiber_id(state[:fiber]),
|
74
|
+
path: state[:path],
|
75
|
+
lineno: state[:lineno]
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def fiber_id(fiber)
|
80
|
+
{
|
81
|
+
object_id: fiber.object_id,
|
82
|
+
tag: fiber.tag
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def fiber_representation(fiber)
|
87
|
+
{
|
88
|
+
object_id: fiber.object_id,
|
89
|
+
tag: fiber.tag,
|
90
|
+
parent: fiber.parent && fiber_id(fiber.parent),
|
91
|
+
children: fiber.children.map { |c| fiber_id(c) }
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_next_command(info)
|
96
|
+
@server.get_command(info)
|
97
|
+
end
|
98
|
+
|
99
|
+
def cmd_initial(cmd)
|
100
|
+
get_next_command(nil)
|
101
|
+
end
|
102
|
+
|
103
|
+
def info_listing(state)
|
104
|
+
{
|
105
|
+
kind: :listing,
|
106
|
+
fiber: fiber_id(state[:fiber]),
|
107
|
+
path: state[:path],
|
108
|
+
lineno: state[:lineno]
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def info_state(state)
|
113
|
+
info_listing(state).merge(
|
114
|
+
kind: :state,
|
115
|
+
fibers: info_fiber_states(state[:fibers])
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
def info_fiber_states(fiber_states)
|
120
|
+
fiber_states.inject({}) do |h, (f, s)|
|
121
|
+
h[fiber_id(f)] = {
|
122
|
+
stack: s[:stack].map { |e| { path: e[:path], lineno: e[:lineno] } }
|
123
|
+
}
|
124
|
+
h
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def cmd_step(cmd)
|
129
|
+
tp = nil
|
130
|
+
fiber = nil
|
131
|
+
while true
|
132
|
+
event = get_next_trace_event
|
133
|
+
@peer = event[:fiber]
|
134
|
+
if event[:kind] == :line && event[:path] !~ /#{POLYPHONY_LIB_DIR}/
|
135
|
+
return get_next_command(info_listing(@state))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
rescue => e
|
139
|
+
trace "Uncaught error: #{e.inspect}"
|
140
|
+
@trace&.disable
|
141
|
+
end
|
142
|
+
|
143
|
+
def cmd_help(cmd)
|
144
|
+
get_next_command(kind: :help)
|
145
|
+
end
|
146
|
+
|
147
|
+
def cmd_list(cmd)
|
148
|
+
get_next_command(info_listing(@state))
|
149
|
+
end
|
150
|
+
|
151
|
+
def cmd_state(cmd)
|
152
|
+
get_next_command(info_state(@state))
|
153
|
+
end
|
154
|
+
|
155
|
+
def handle_tp(trace, tp)
|
156
|
+
return if Thread.current == @server.thread
|
157
|
+
return if Fiber.current == @control_fiber
|
158
|
+
|
159
|
+
kind = tp.event
|
160
|
+
event = {
|
161
|
+
fiber: Fiber.current,
|
162
|
+
kind: kind,
|
163
|
+
path: tp.path,
|
164
|
+
lineno: tp.lineno,
|
165
|
+
binding: tp.binding
|
166
|
+
}
|
167
|
+
case kind
|
168
|
+
when :call, :c_call, :b_call
|
169
|
+
event[:method_id] = tp.method_id
|
170
|
+
event[:parameters] = tp.parameters
|
171
|
+
when :return, :c_return, :b_return
|
172
|
+
event[:method_id] = tp.method_id
|
173
|
+
event[:return_value] = tp.return_value
|
174
|
+
end
|
175
|
+
@control_fiber.transfer(event)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class DebugServer
|
180
|
+
attr_reader :thread
|
181
|
+
|
182
|
+
def initialize(socket_path)
|
183
|
+
@socket_path = socket_path
|
184
|
+
@fiber = Fiber.current
|
185
|
+
start_server_thread
|
186
|
+
end
|
187
|
+
|
188
|
+
def start_server_thread
|
189
|
+
@thread = Thread.new do
|
190
|
+
puts("Listening on #{@socket_path}")
|
191
|
+
FileUtils.rm(@socket_path) if File.exists?(@socket_path)
|
192
|
+
socket = UNIXServer.new(@socket_path)
|
193
|
+
loop do
|
194
|
+
@client = socket.accept
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def stop
|
200
|
+
@thread.kill
|
201
|
+
end
|
202
|
+
|
203
|
+
def handle_client(client)
|
204
|
+
@client = client
|
205
|
+
end
|
206
|
+
|
207
|
+
def wait_for_client
|
208
|
+
sleep 0.1 until @client
|
209
|
+
msg = @client.gets
|
210
|
+
@client.puts msg
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_command(info)
|
214
|
+
@client&.orig_write "#{info.inspect}\n"
|
215
|
+
cmd = @client&.orig_gets&.chomp
|
216
|
+
eval(cmd)
|
217
|
+
rescue SystemCallError
|
218
|
+
nil
|
219
|
+
rescue => e
|
220
|
+
trace "Error in interact_with_client: #{e.inspect}"
|
221
|
+
e.backtrace[0..3].each { |l| trace l }
|
222
|
+
@client = nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -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
|
|
@@ -75,47 +79,26 @@ module Polyphony
|
|
75
79
|
|
76
80
|
# Fiber supervision
|
77
81
|
module FiberSupervision
|
78
|
-
def supervise(opts
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
while true
|
84
|
-
supervise_perform(opts)
|
85
|
-
end
|
86
|
-
rescue Polyphony::MoveOn
|
87
|
-
# generated in #supervise_perform to stop supervisor
|
88
|
-
ensure
|
89
|
-
@on_child_done = nil
|
90
|
-
end
|
82
|
+
def supervise(*fibers, **opts, &block)
|
83
|
+
block ||= opts[:on_done] ||
|
84
|
+
(opts[:on_error] && supervise_on_error_proc(opts[:on_error]))
|
85
|
+
raise "No block given" unless block
|
91
86
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
restart_fiber(fiber, opts)
|
96
|
-
elsif Fiber.current.children.empty?
|
97
|
-
Fiber.current.stop
|
87
|
+
fibers.each do |f|
|
88
|
+
f.attach_to(self) unless f.parent == self
|
89
|
+
f.monitor(self)
|
98
90
|
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
|
103
91
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
92
|
+
mailbox = monitor_mailbox
|
93
|
+
|
94
|
+
while true
|
95
|
+
(fiber, result) = mailbox.shift
|
96
|
+
block&.call(fiber, result)
|
108
97
|
end
|
109
98
|
end
|
110
99
|
|
111
|
-
def
|
112
|
-
opts[:
|
113
|
-
case opts[:restart]
|
114
|
-
when true
|
115
|
-
fiber.restart
|
116
|
-
when :one_for_all
|
117
|
-
@children.keys.each(&:restart)
|
118
|
-
end
|
100
|
+
def supervise_on_error_proc(on_error)
|
101
|
+
->(f, r) { opts[:on_error].(f, r) if r.is_a?(Exception) }
|
119
102
|
end
|
120
103
|
end
|
121
104
|
|
@@ -124,23 +107,24 @@ module Polyphony
|
|
124
107
|
def await(*fibers)
|
125
108
|
return [] if fibers.empty?
|
126
109
|
|
127
|
-
|
110
|
+
current_fiber = self.current
|
111
|
+
mailbox = current_fiber.monitor_mailbox
|
128
112
|
results = {}
|
129
113
|
fibers.each do |f|
|
130
114
|
results[f] = nil
|
131
115
|
if f.dead?
|
132
116
|
# fiber already terminated, so queue message
|
133
|
-
|
117
|
+
mailbox << [f, f.result]
|
134
118
|
else
|
135
|
-
f.monitor
|
119
|
+
f.monitor(current_fiber)
|
136
120
|
end
|
137
121
|
end
|
138
122
|
exception = nil
|
139
123
|
while !fibers.empty?
|
140
|
-
(fiber, result) =
|
124
|
+
(fiber, result) = mailbox.shift
|
141
125
|
next unless fibers.include?(fiber)
|
142
|
-
|
143
126
|
fibers.delete(fiber)
|
127
|
+
current_fiber.remove_child(fiber) if fiber.parent == current_fiber
|
144
128
|
if result.is_a?(Exception)
|
145
129
|
exception ||= result
|
146
130
|
fibers.each { |f| f.terminate }
|
@@ -148,16 +132,16 @@ module Polyphony
|
|
148
132
|
results[fiber] = result
|
149
133
|
end
|
150
134
|
end
|
151
|
-
results.values
|
152
|
-
ensure
|
153
|
-
Fiber.current.message_on_child_termination = false
|
154
135
|
raise exception if exception
|
136
|
+
results.values
|
155
137
|
end
|
156
138
|
alias_method :join, :await
|
157
139
|
|
158
140
|
def select(*fibers)
|
159
141
|
return nil if fibers.empty?
|
160
142
|
|
143
|
+
current_fiber = self.current
|
144
|
+
mailbox = current_fiber.monitor_mailbox
|
161
145
|
fibers.each do |f|
|
162
146
|
if f.dead?
|
163
147
|
result = f.result
|
@@ -165,21 +149,18 @@ module Polyphony
|
|
165
149
|
end
|
166
150
|
end
|
167
151
|
|
168
|
-
|
169
|
-
fibers.each { |f| f.monitor }
|
152
|
+
fibers.each { |f| f.monitor(current_fiber) }
|
170
153
|
while true
|
171
|
-
(fiber, result) =
|
154
|
+
(fiber, result) = mailbox.shift
|
172
155
|
next unless fibers.include?(fiber)
|
173
156
|
|
174
|
-
fibers.each { |f| f.unmonitor }
|
157
|
+
fibers.each { |f| f.unmonitor(current_fiber) }
|
175
158
|
if result.is_a?(Exception)
|
176
159
|
raise result
|
177
160
|
else
|
178
161
|
return [fiber, result]
|
179
162
|
end
|
180
163
|
end
|
181
|
-
ensure
|
182
|
-
Fiber.current.message_on_child_termination = false
|
183
164
|
end
|
184
165
|
|
185
166
|
# Creates and schedules with priority an out-of-band fiber that runs the
|
@@ -219,14 +200,6 @@ module Polyphony
|
|
219
200
|
f
|
220
201
|
end
|
221
202
|
|
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
203
|
def terminate_all_children(graceful = false)
|
231
204
|
return unless @children
|
232
205
|
|
@@ -240,16 +213,25 @@ module Polyphony
|
|
240
213
|
def await_all_children
|
241
214
|
return unless @children && !@children.empty?
|
242
215
|
|
243
|
-
Fiber.await(*@children.keys)
|
216
|
+
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
244
217
|
end
|
245
218
|
|
246
219
|
def shutdown_all_children(graceful = false)
|
247
220
|
return unless @children
|
248
221
|
|
249
222
|
@children.keys.each do |c|
|
223
|
+
next if c.dead?
|
224
|
+
|
250
225
|
c.terminate(graceful)
|
251
226
|
c.await
|
252
227
|
end
|
228
|
+
reap_dead_children
|
229
|
+
end
|
230
|
+
|
231
|
+
def reap_dead_children
|
232
|
+
return unless @children
|
233
|
+
|
234
|
+
@children.reject! { |f| f.dead? }
|
253
235
|
end
|
254
236
|
|
255
237
|
def detach
|
@@ -258,10 +240,17 @@ module Polyphony
|
|
258
240
|
@parent.add_child(self)
|
259
241
|
end
|
260
242
|
|
261
|
-
def
|
243
|
+
def attach_to(fiber)
|
262
244
|
@parent.remove_child(self)
|
263
|
-
@parent =
|
264
|
-
|
245
|
+
@parent = fiber
|
246
|
+
fiber.add_child(self)
|
247
|
+
end
|
248
|
+
|
249
|
+
def attach_and_monitor(fiber)
|
250
|
+
@parent.remove_child(self)
|
251
|
+
@parent = fiber
|
252
|
+
fiber.add_child(self)
|
253
|
+
monitor(fiber)
|
265
254
|
end
|
266
255
|
end
|
267
256
|
|
@@ -323,7 +312,7 @@ module Polyphony
|
|
323
312
|
Thread.backend.trace(:fiber_terminate, self, result)
|
324
313
|
@result = result
|
325
314
|
|
326
|
-
|
315
|
+
inform_monitors(result, uncaught_exception)
|
327
316
|
@running = false
|
328
317
|
ensure
|
329
318
|
# Prevent fiber from being resumed after terminating
|
@@ -341,24 +330,28 @@ module Polyphony
|
|
341
330
|
[e, true]
|
342
331
|
end
|
343
332
|
|
344
|
-
def
|
333
|
+
def inform_monitors(result, uncaught_exception)
|
345
334
|
if @monitors
|
346
335
|
msg = [self, result]
|
347
|
-
@monitors.
|
336
|
+
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
348
337
|
end
|
349
338
|
|
350
|
-
@parent
|
339
|
+
if uncaught_exception && @parent
|
340
|
+
parent_is_monitor = @monitors&.has_key?(@parent)
|
341
|
+
@parent.schedule_with_priority(result) unless parent_is_monitor
|
342
|
+
end
|
351
343
|
end
|
352
344
|
|
353
|
-
|
345
|
+
def monitor(fiber)
|
346
|
+
(@monitors ||= {})[fiber] = true
|
347
|
+
end
|
354
348
|
|
355
|
-
def
|
356
|
-
@monitors ||= []
|
357
|
-
@monitors << Fiber.current
|
349
|
+
def unmonitor(fiber)
|
350
|
+
(@monitors ||= []).delete(fiber)
|
358
351
|
end
|
359
352
|
|
360
|
-
def
|
361
|
-
@monitors
|
353
|
+
def monitors
|
354
|
+
@monitors&.keys || []
|
362
355
|
end
|
363
356
|
|
364
357
|
def dead?
|