polyphony 0.63 → 0.67
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 +19 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -40
- data/bin/pdbg +30 -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 +84 -24
- 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/server.rb +137 -0
- data/lib/polyphony/extensions/debug.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +40 -33
- data/lib/polyphony/extensions/io.rb +6 -2
- data/lib/polyphony/extensions/openssl.rb +8 -2
- data/lib/polyphony/extensions/socket.rb +15 -10
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +6 -4
- data/test/stress.rb +6 -2
- data/test/test_backend.rb +13 -4
- data/test/test_fiber.rb +33 -9
- 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_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 +4 -2
@@ -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/server'
|
128
|
+
Polyphony::DebugServer.start(debug_socket_path)
|
129
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony/extensions/debug'
|
4
|
+
|
5
|
+
module Polyphony
|
6
|
+
class DebugServer
|
7
|
+
TP_EVENTS = [
|
8
|
+
:line,
|
9
|
+
:call,
|
10
|
+
:return,
|
11
|
+
:b_call,
|
12
|
+
:b_return
|
13
|
+
]
|
14
|
+
|
15
|
+
|
16
|
+
def self.start(socket_path)
|
17
|
+
server = self.new(socket_path)
|
18
|
+
server.start
|
19
|
+
|
20
|
+
trace = TracePoint.new(*TP_EVENTS) { |tp| server.handle_tp(trace, tp) }
|
21
|
+
trace.enable
|
22
|
+
|
23
|
+
at_exit do
|
24
|
+
puts "program terminated"
|
25
|
+
trace.disable
|
26
|
+
server.stop
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(socket_path)
|
31
|
+
@socket_path = socket_path
|
32
|
+
@fiber = Fiber.current
|
33
|
+
@controller = spin { control_loop }
|
34
|
+
puts "@fiber: #{@fiber.inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
fiber = Fiber.current
|
39
|
+
@server = spin(:pdbg_server) do
|
40
|
+
puts("Listening on #{@socket_path}")
|
41
|
+
FileUtils.rm(@socket_path) if File.exists?(@socket_path)
|
42
|
+
socket = UNIXServer.new(@socket_path)
|
43
|
+
fiber << :ready
|
44
|
+
id = 0
|
45
|
+
socket.accept_loop do |client|
|
46
|
+
puts "accepted connection"
|
47
|
+
handle_client(client)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
receive
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
@server.terminate
|
55
|
+
@controller.terminate
|
56
|
+
end
|
57
|
+
|
58
|
+
POLYPHONY_LIB_DIR = File.expand_path('../..', __dir__)
|
59
|
+
def handle_client(client)
|
60
|
+
@client = client
|
61
|
+
puts "trace enabled"
|
62
|
+
end
|
63
|
+
|
64
|
+
def control_loop
|
65
|
+
@cmd = :step
|
66
|
+
loop do
|
67
|
+
case @cmd
|
68
|
+
when :step
|
69
|
+
step
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def step
|
75
|
+
tp = nil
|
76
|
+
fiber = nil
|
77
|
+
while true
|
78
|
+
event = receive
|
79
|
+
fiber = event[:fiber]
|
80
|
+
if fiber == @fiber && event[:kind] == :line && event[:path] !~ /#{POLYPHONY_LIB_DIR}/
|
81
|
+
interact_with_client(event)
|
82
|
+
fiber << :ok
|
83
|
+
fiber.__unpark__
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
fiber << :ok
|
88
|
+
fiber.__unpark__
|
89
|
+
end
|
90
|
+
rescue => e
|
91
|
+
puts "Uncaught error: #{e.inspect}"
|
92
|
+
@trace&.disable
|
93
|
+
@client = nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def interact_with_client(event)
|
97
|
+
@client.puts event.inspect
|
98
|
+
result = @client.gets&.chomp
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_tp(trace, tp)
|
102
|
+
return if @in_handle_tp
|
103
|
+
|
104
|
+
process_tp(trace, tp)
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_tp(trace, tp)
|
108
|
+
@in_handle_tp = true
|
109
|
+
if !@client
|
110
|
+
wait_for_client
|
111
|
+
end
|
112
|
+
|
113
|
+
puts "- #{tp.event} #{tp.path}:#{tp.lineno}"
|
114
|
+
|
115
|
+
fiber = Fiber.current
|
116
|
+
fiber.__park__
|
117
|
+
|
118
|
+
@controller << {
|
119
|
+
fiber: fiber,
|
120
|
+
kind: tp.event,
|
121
|
+
path: tp.path,
|
122
|
+
lineno: tp.lineno
|
123
|
+
}
|
124
|
+
receive
|
125
|
+
ensure
|
126
|
+
@in_handle_tp = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def wait_for_client
|
130
|
+
puts "wait_for_client"
|
131
|
+
sleep 0.1 until @client
|
132
|
+
puts " got client!"
|
133
|
+
msg = @client.gets
|
134
|
+
@client.puts msg
|
135
|
+
end
|
136
|
+
end
|
137
|
+
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
|
|
@@ -124,23 +128,24 @@ module Polyphony
|
|
124
128
|
def await(*fibers)
|
125
129
|
return [] if fibers.empty?
|
126
130
|
|
127
|
-
|
131
|
+
current_fiber = self.current
|
132
|
+
mailbox = current_fiber.monitor_mailbox
|
128
133
|
results = {}
|
129
134
|
fibers.each do |f|
|
130
135
|
results[f] = nil
|
131
136
|
if f.dead?
|
132
137
|
# fiber already terminated, so queue message
|
133
|
-
|
138
|
+
mailbox << [f, f.result]
|
134
139
|
else
|
135
|
-
f.monitor
|
140
|
+
f.monitor(current_fiber)
|
136
141
|
end
|
137
142
|
end
|
138
143
|
exception = nil
|
139
144
|
while !fibers.empty?
|
140
|
-
(fiber, result) =
|
145
|
+
(fiber, result) = mailbox.shift
|
141
146
|
next unless fibers.include?(fiber)
|
142
|
-
|
143
147
|
fibers.delete(fiber)
|
148
|
+
current_fiber.remove_child(fiber) if fiber.parent == current_fiber
|
144
149
|
if result.is_a?(Exception)
|
145
150
|
exception ||= result
|
146
151
|
fibers.each { |f| f.terminate }
|
@@ -148,16 +153,16 @@ module Polyphony
|
|
148
153
|
results[fiber] = result
|
149
154
|
end
|
150
155
|
end
|
151
|
-
results.values
|
152
|
-
ensure
|
153
|
-
Fiber.current.message_on_child_termination = false
|
154
156
|
raise exception if exception
|
157
|
+
results.values
|
155
158
|
end
|
156
159
|
alias_method :join, :await
|
157
160
|
|
158
161
|
def select(*fibers)
|
159
162
|
return nil if fibers.empty?
|
160
163
|
|
164
|
+
current_fiber = self.current
|
165
|
+
mailbox = current_fiber.monitor_mailbox
|
161
166
|
fibers.each do |f|
|
162
167
|
if f.dead?
|
163
168
|
result = f.result
|
@@ -165,21 +170,18 @@ module Polyphony
|
|
165
170
|
end
|
166
171
|
end
|
167
172
|
|
168
|
-
|
169
|
-
fibers.each { |f| f.monitor }
|
173
|
+
fibers.each { |f| f.monitor(current_fiber) }
|
170
174
|
while true
|
171
|
-
(fiber, result) =
|
175
|
+
(fiber, result) = mailbox.shift
|
172
176
|
next unless fibers.include?(fiber)
|
173
177
|
|
174
|
-
fibers.each { |f| f.unmonitor }
|
178
|
+
fibers.each { |f| f.unmonitor(current_fiber) }
|
175
179
|
if result.is_a?(Exception)
|
176
180
|
raise result
|
177
181
|
else
|
178
182
|
return [fiber, result]
|
179
183
|
end
|
180
184
|
end
|
181
|
-
ensure
|
182
|
-
Fiber.current.message_on_child_termination = false
|
183
185
|
end
|
184
186
|
|
185
187
|
# Creates and schedules with priority an out-of-band fiber that runs the
|
@@ -219,14 +221,6 @@ module Polyphony
|
|
219
221
|
f
|
220
222
|
end
|
221
223
|
|
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
224
|
def terminate_all_children(graceful = false)
|
231
225
|
return unless @children
|
232
226
|
|
@@ -240,16 +234,25 @@ module Polyphony
|
|
240
234
|
def await_all_children
|
241
235
|
return unless @children && !@children.empty?
|
242
236
|
|
243
|
-
Fiber.await(*@children.keys)
|
237
|
+
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
244
238
|
end
|
245
239
|
|
246
240
|
def shutdown_all_children(graceful = false)
|
247
241
|
return unless @children
|
248
242
|
|
249
243
|
@children.keys.each do |c|
|
244
|
+
next if c.dead?
|
245
|
+
|
250
246
|
c.terminate(graceful)
|
251
247
|
c.await
|
252
248
|
end
|
249
|
+
reap_dead_children
|
250
|
+
end
|
251
|
+
|
252
|
+
def reap_dead_children
|
253
|
+
return unless @children
|
254
|
+
|
255
|
+
@children.reject! { |f| f.dead? }
|
253
256
|
end
|
254
257
|
|
255
258
|
def detach
|
@@ -323,7 +326,7 @@ module Polyphony
|
|
323
326
|
Thread.backend.trace(:fiber_terminate, self, result)
|
324
327
|
@result = result
|
325
328
|
|
326
|
-
|
329
|
+
inform_monitors(result, uncaught_exception)
|
327
330
|
@running = false
|
328
331
|
ensure
|
329
332
|
# Prevent fiber from being resumed after terminating
|
@@ -341,24 +344,28 @@ module Polyphony
|
|
341
344
|
[e, true]
|
342
345
|
end
|
343
346
|
|
344
|
-
def
|
347
|
+
def inform_monitors(result, uncaught_exception)
|
345
348
|
if @monitors
|
346
349
|
msg = [self, result]
|
347
|
-
@monitors.
|
350
|
+
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
348
351
|
end
|
349
352
|
|
350
|
-
@parent
|
353
|
+
if uncaught_exception && @parent
|
354
|
+
parent_is_monitor = @monitors&.has_key?(@parent)
|
355
|
+
@parent.schedule_with_priority(result) unless parent_is_monitor
|
356
|
+
end
|
351
357
|
end
|
352
358
|
|
353
|
-
|
359
|
+
def monitor(fiber)
|
360
|
+
(@monitors ||= {})[fiber] = true
|
361
|
+
end
|
354
362
|
|
355
|
-
def
|
356
|
-
@monitors ||= []
|
357
|
-
@monitors << Fiber.current
|
363
|
+
def unmonitor(fiber)
|
364
|
+
(@monitors ||= []).delete(fiber)
|
358
365
|
end
|
359
366
|
|
360
|
-
def
|
361
|
-
@monitors
|
367
|
+
def monitors
|
368
|
+
@monitors&.keys || []
|
362
369
|
end
|
363
370
|
|
364
371
|
def dead?
|
@@ -76,6 +76,10 @@ end
|
|
76
76
|
|
77
77
|
# IO instance method patches
|
78
78
|
class ::IO
|
79
|
+
def __polyphony_read_method__
|
80
|
+
:backend_read
|
81
|
+
end
|
82
|
+
|
79
83
|
# def each(sep = $/, limit = nil, chomp: nil)
|
80
84
|
# sep, limit = $/, sep if sep.is_a?(Integer)
|
81
85
|
# end
|
@@ -123,9 +127,9 @@ class ::IO
|
|
123
127
|
end
|
124
128
|
|
125
129
|
alias_method :orig_readpartial, :read
|
126
|
-
def readpartial(len, str = +'', buffer_pos = 0)
|
130
|
+
def readpartial(len, str = +'', buffer_pos = 0, raise_on_eof = true)
|
127
131
|
result = Polyphony.backend_read(self, str, len, false, buffer_pos)
|
128
|
-
raise EOFError
|
132
|
+
raise EOFError if !result && raise_on_eof
|
129
133
|
|
130
134
|
result
|
131
135
|
end
|
@@ -5,6 +5,10 @@ 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 __polyphony_read_method__
|
9
|
+
:readpartial
|
10
|
+
end
|
11
|
+
|
8
12
|
alias_method :orig_initialize, :initialize
|
9
13
|
def initialize(socket, context = nil)
|
10
14
|
socket = socket.respond_to?(:io) ? socket.io || socket : socket
|
@@ -78,7 +82,7 @@ class ::OpenSSL::SSL::SSLSocket
|
|
78
82
|
buf
|
79
83
|
end
|
80
84
|
|
81
|
-
def readpartial(maxlen, buf = +'', buffer_pos = 0)
|
85
|
+
def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
|
82
86
|
if buffer_pos != 0
|
83
87
|
if (result = sysread(maxlen, +''))
|
84
88
|
if buffer_pos == -1
|
@@ -90,7 +94,9 @@ class ::OpenSSL::SSL::SSLSocket
|
|
90
94
|
else
|
91
95
|
result = sysread(maxlen, buf)
|
92
96
|
end
|
93
|
-
|
97
|
+
|
98
|
+
raise EOFError if !result && raise_on_eof
|
99
|
+
result
|
94
100
|
end
|
95
101
|
|
96
102
|
def read_loop(maxlen = 8192)
|
@@ -5,6 +5,12 @@ require 'socket'
|
|
5
5
|
require_relative './io'
|
6
6
|
require_relative '../core/thread_pool'
|
7
7
|
|
8
|
+
class BasicSocket
|
9
|
+
def __polyphony_read_method__
|
10
|
+
:backend_recv
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
# Socket overrides (eventually rewritten in C)
|
9
15
|
class ::Socket
|
10
16
|
def accept
|
@@ -77,8 +83,9 @@ class ::Socket
|
|
77
83
|
# Polyphony.backend_send(self, mesg, 0)
|
78
84
|
# end
|
79
85
|
|
80
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
81
|
-
Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
86
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
87
|
+
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
88
|
+
raise EOFError if !result && raise_on_eof
|
82
89
|
end
|
83
90
|
|
84
91
|
ZERO_LINGER = [0, 0].pack('ii').freeze
|
@@ -199,11 +206,10 @@ class ::TCPSocket
|
|
199
206
|
# Polyphony.backend_send(self, mesg, 0)
|
200
207
|
# end
|
201
208
|
|
202
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
209
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
203
210
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
204
|
-
raise EOFError
|
205
|
-
|
206
|
-
str
|
211
|
+
raise EOFError if !result && raise_on_eof
|
212
|
+
result
|
207
213
|
end
|
208
214
|
|
209
215
|
def read_nonblock(len, str = nil, exception: true)
|
@@ -293,11 +299,10 @@ class ::UNIXSocket
|
|
293
299
|
Polyphony.backend_send(self, mesg, 0)
|
294
300
|
end
|
295
301
|
|
296
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0)
|
302
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
297
303
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
298
|
-
raise EOFError
|
299
|
-
|
300
|
-
str
|
304
|
+
raise EOFError if !result && raise_on_eof
|
305
|
+
result
|
301
306
|
end
|
302
307
|
|
303
308
|
def read_nonblock(len, str = nil, exception: true)
|