polyphony 0.67 → 0.71
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 +21 -1
- data/Gemfile.lock +2 -2
- data/TODO.md +0 -23
- data/bin/pdbg +91 -9
- data/ext/polyphony/backend_common.c +18 -0
- data/ext/polyphony/backend_common.h +1 -0
- data/ext/polyphony/backend_io_uring.c +2 -8
- data/ext/polyphony/backend_libev.c +2 -0
- data/lib/polyphony.rb +2 -2
- data/lib/polyphony/debugger.rb +225 -0
- data/lib/polyphony/extensions/fiber.rb +33 -43
- 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 +0 -1
- data/test/stress.rb +1 -1
- data/test/test_fiber.rb +23 -7
- data/test/test_global_api.rb +0 -1
- data/test/test_process_supervision.rb +38 -9
- data/test/test_supervise.rb +183 -100
- metadata +4 -4
- data/lib/polyphony/debugger/server.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36566e2f3d8ca8535569ec2766183295a6b17f3af67123a6442242881b004ef8
|
4
|
+
data.tar.gz: d38059a39ce762cb043c68374829050b6abd4596fc900f68d24120e843efa9db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d85656e6ca42458d262433bed6817e6fe5d60b194fddce269fae9ab9d479a7167b3460ac01cdb3c3018d9e3bf3b61ea5560aa2f186dedb841ac81dec311ffb5
|
7
|
+
data.tar.gz: 6db9f437744ece1eeae09c738ac31172439472acd21e8956e75defbdf4b113b5fd61e04ff73ef9f29046b54baf44e0064350b6c04df9883c34b16efd9bad35e1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
## 0.71 2021-08-20
|
2
|
+
|
3
|
+
- Fix compilation on Ruby 3.0
|
4
|
+
|
5
|
+
## 0.70 2021-08-19
|
6
|
+
|
7
|
+
- New implementation for `#supervise`
|
8
|
+
- Reset backend state and runqueue on fork
|
9
|
+
|
10
|
+
## 0.69 2021-08-16
|
11
|
+
|
12
|
+
- Rename `#__polyphony_read_method__` to `#__parser_read_method__`
|
13
|
+
|
14
|
+
## 0.68 2021-08-13
|
15
|
+
|
16
|
+
- Fix missing default value in socket classes' `#readpartial`
|
17
|
+
- Fix linking of operations in `Backend#chain` (io_uring version)
|
18
|
+
- Rename `Fiber#attach` to `Fiber#attach_to`
|
19
|
+
- Expose original `SSLServer#accept`
|
20
|
+
|
1
21
|
## 0.67 2021-08-06
|
2
22
|
|
3
23
|
- Improve fiber monitoring
|
@@ -11,7 +31,7 @@
|
|
11
31
|
|
12
32
|
## 0.65 2021-07-29
|
13
33
|
|
14
|
-
- Add `#
|
34
|
+
- Add `#__parser_read_method__` method for read method detection
|
15
35
|
|
16
36
|
## 0.64 2021-07-26
|
17
37
|
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -26,31 +26,8 @@
|
|
26
26
|
|
27
27
|
- Add support for `close` to io_uring backend
|
28
28
|
|
29
|
-
- Graceful shutdown again:
|
30
|
-
- What happens to children when doing a graceful shutdown?
|
31
|
-
- What are the implications of passing graceful shutdown flag to children?
|
32
|
-
- What about errors while doing a graceful shutdown?
|
33
|
-
- What about graceful restarts?
|
34
|
-
- Some interesting discussions:
|
35
|
-
- https://trio.discourse.group/search?q=graceful%20shutdown
|
36
|
-
- https://github.com/python-trio/trio/issues/147
|
37
|
-
- https://github.com/python-trio/trio/issues/143
|
38
|
-
- https://trio.discourse.group/t/graceful-shutdown/93/2
|
39
|
-
- https://250bpm.com/blog:146/
|
40
|
-
- https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
|
41
|
-
- https://github.com/tj/go-gracefully
|
42
|
-
- `Fiber#finalize_children` should pass graceful shutdown flag to children
|
43
|
-
- A good use case is an HTTP server that on graceful shutdown:
|
44
|
-
- stops listening
|
45
|
-
- waits for all ongoing requests to finish, optionally with a timeout
|
46
|
-
|
47
29
|
## Roadmap for Polyphony 1.0
|
48
30
|
|
49
|
-
- check integration with rb-inotify
|
50
|
-
|
51
|
-
- Improve `#supervise`. It does not work as advertised, and seems to exhibit an
|
52
|
-
inconsistent behaviour (see supervisor example).
|
53
|
-
|
54
31
|
- Add test that mimics the original design for Monocrono:
|
55
32
|
- 256 fibers each waiting for a message
|
56
33
|
- When message received do some blocking work using a `ThreadPool`
|
data/bin/pdbg
CHANGED
@@ -8,23 +8,105 @@ UNIX_SOCKET_PATH = '/tmp/pdbg.sock'
|
|
8
8
|
|
9
9
|
cmd = ARGV.join(' ')
|
10
10
|
injected_lib_path = File.expand_path('../lib/polyphony/debugger/server_inject.rb', __dir__)
|
11
|
-
p cmd
|
12
11
|
pid = fork { exec("env POLYPHONY_DEBUG_SOCKET_PATH=#{UNIX_SOCKET_PATH} ruby #{cmd}") }
|
13
12
|
puts "Started debugged process (#{pid})"
|
14
13
|
|
15
|
-
|
16
|
-
socket
|
14
|
+
socket = nil
|
15
|
+
while !socket
|
16
|
+
socket = UNIXSocket.new(UNIX_SOCKET_PATH) rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_command(cmd)
|
20
|
+
case cmd
|
21
|
+
when /^(step|s)$/
|
22
|
+
{ cmd: :step }
|
23
|
+
when /^(state|st)$/
|
24
|
+
{ cmd: :state }
|
25
|
+
when /^(help|h)$/
|
26
|
+
{ cmd: :help }
|
27
|
+
when /^(list|l)$/
|
28
|
+
{ cmd: :list }
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def display_info(info)
|
35
|
+
info = eval(info)
|
36
|
+
case (info && info[:kind])
|
37
|
+
when :listing
|
38
|
+
print_listing(info)
|
39
|
+
when :state
|
40
|
+
print_state(info)
|
41
|
+
else
|
42
|
+
p info
|
43
|
+
end
|
44
|
+
rescue SyntaxError
|
45
|
+
puts "Failed to eval:"
|
46
|
+
p info
|
47
|
+
end
|
48
|
+
|
49
|
+
FILE_LINES_CACHE = {}
|
50
|
+
|
51
|
+
def self.get_snippet(path, lineno)
|
52
|
+
lines = FILE_LINES_CACHE[path] ||= IO.read(path).lines
|
53
|
+
start_idx = lineno - 5
|
54
|
+
stop_idx = lineno + 3
|
55
|
+
stop_idx = lines.size - 1 if stop_idx >= lines.size
|
56
|
+
start_idx = 0 if start_idx < 0
|
57
|
+
(start_idx..stop_idx).map { |idx| [idx + 1, lines[idx]]}
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_snippet(info, snippet, cur_line)
|
61
|
+
places = FILE_LINES_CACHE[info[:path]].size.to_s.size
|
62
|
+
snippet.each do |(lineno, line)|
|
63
|
+
is_cur = lineno == cur_line
|
64
|
+
formatted = format("%s% #{places}d %s", is_cur ? '=> ' : ' ', lineno, line)
|
65
|
+
puts formatted
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def print_listing(info)
|
70
|
+
snippet = get_snippet(info[:path], info[:lineno])
|
71
|
+
puts "Fiber: #{info[:fiber]} Location: #{info[:path]}:#{info[:lineno]}"
|
72
|
+
puts
|
73
|
+
print_snippet(info, snippet, info[:lineno])
|
74
|
+
puts
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_help
|
78
|
+
puts
|
79
|
+
puts "Here's some help..."
|
80
|
+
puts
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_state(info)
|
84
|
+
p info
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_user_cmd
|
88
|
+
while true
|
89
|
+
STDOUT << "(pdbg) "
|
90
|
+
cmd = parse_command(STDIN.gets)
|
91
|
+
next unless cmd
|
92
|
+
|
93
|
+
if cmd[:cmd] == :help
|
94
|
+
print_help
|
95
|
+
else
|
96
|
+
return cmd if cmd
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
17
101
|
socket.puts 'pdbg'
|
18
102
|
response = socket.gets
|
19
103
|
if response.chomp == 'pdbg'
|
20
104
|
puts 'Connected to process'
|
21
105
|
end
|
22
106
|
loop do
|
23
|
-
|
24
|
-
|
107
|
+
info = socket.gets.chomp
|
108
|
+
display_info(info)
|
25
109
|
|
26
|
-
|
27
|
-
cmd
|
28
|
-
puts '-' * 40
|
29
|
-
socket.puts cmd
|
110
|
+
cmd = get_user_cmd
|
111
|
+
socket.puts cmd.inspect
|
30
112
|
end
|
@@ -31,6 +31,24 @@ inline void backend_base_mark(struct Backend_base *base) {
|
|
31
31
|
runqueue_mark(&base->parked_runqueue);
|
32
32
|
}
|
33
33
|
|
34
|
+
void backend_base_reset(struct Backend_base *base) {
|
35
|
+
runqueue_finalize(&base->runqueue);
|
36
|
+
runqueue_finalize(&base->parked_runqueue);
|
37
|
+
|
38
|
+
runqueue_initialize(&base->runqueue);
|
39
|
+
runqueue_initialize(&base->parked_runqueue);
|
40
|
+
|
41
|
+
base->currently_polling = 0;
|
42
|
+
base->op_count = 0;
|
43
|
+
base->switch_count = 0;
|
44
|
+
base->poll_count = 0;
|
45
|
+
base->pending_count = 0;
|
46
|
+
base->idle_gc_period = 0;
|
47
|
+
base->idle_gc_last_time = 0;
|
48
|
+
base->idle_proc = Qnil;
|
49
|
+
base->trace_proc = Qnil;
|
50
|
+
}
|
51
|
+
|
34
52
|
const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
|
35
53
|
|
36
54
|
inline void conditional_nonblocking_poll(VALUE backend, struct Backend_base *base, VALUE current, VALUE next) {
|
@@ -32,6 +32,7 @@ struct Backend_base {
|
|
32
32
|
void backend_base_initialize(struct Backend_base *base);
|
33
33
|
void backend_base_finalize(struct Backend_base *base);
|
34
34
|
void backend_base_mark(struct Backend_base *base);
|
35
|
+
void backend_base_reset(struct Backend_base *base);
|
35
36
|
VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
|
36
37
|
void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
|
37
38
|
void backend_base_park_fiber(struct Backend_base *base, VALUE fiber);
|
@@ -106,9 +106,7 @@ VALUE Backend_post_fork(VALUE self) {
|
|
106
106
|
io_uring_queue_exit(&backend->ring);
|
107
107
|
io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
|
108
108
|
context_store_free(&backend->store);
|
109
|
-
backend->base
|
110
|
-
backend->base.pending_count = 0;
|
111
|
-
backend->pending_sqes = 0;
|
109
|
+
backend_base_reset(&backend->base);
|
112
110
|
|
113
111
|
return self;
|
114
112
|
}
|
@@ -1248,7 +1246,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
|
|
1248
1246
|
}
|
1249
1247
|
|
1250
1248
|
io_uring_sqe_set_data(last_sqe, ctx);
|
1251
|
-
unsigned int flags = (i == argc - 1) ? IOSQE_ASYNC : IOSQE_ASYNC
|
1249
|
+
unsigned int flags = (i == (argc - 1)) ? IOSQE_ASYNC : IOSQE_ASYNC | IOSQE_IO_LINK;
|
1252
1250
|
io_uring_sqe_set_flags(last_sqe, flags);
|
1253
1251
|
sqe_count++;
|
1254
1252
|
}
|
@@ -1528,10 +1526,6 @@ void Init_Backend() {
|
|
1528
1526
|
rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
|
1529
1527
|
rb_define_method(cBackend, "write", Backend_write_m, -1);
|
1530
1528
|
|
1531
|
-
#ifdef POLYPHONY_UNSET_NONBLOCK
|
1532
|
-
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
1533
|
-
#endif
|
1534
|
-
|
1535
1529
|
SYM_io_uring = ID2SYM(rb_intern("io_uring"));
|
1536
1530
|
SYM_send = ID2SYM(rb_intern("send"));
|
1537
1531
|
SYM_splice = ID2SYM(rb_intern("splice"));
|
data/lib/polyphony.rb
CHANGED
@@ -124,6 +124,6 @@ Polyphony.install_at_exit_handler
|
|
124
124
|
|
125
125
|
if (debug_socket_path = ENV['POLYPHONY_DEBUG_SOCKET_PATH'])
|
126
126
|
puts "Starting debug server on #{debug_socket_path}"
|
127
|
-
require 'polyphony/debugger
|
128
|
-
Polyphony
|
127
|
+
require 'polyphony/debugger'
|
128
|
+
Polyphony.start_debug_server(debug_socket_path)
|
129
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
|