polyphony 0.64 → 0.68
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/.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?
|