polyphony 0.67 → 0.71

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b8553a2f84e79520ce53412ee6891873c9a9d25e0789e82b26a7153d3d7ae67
4
- data.tar.gz: 6889ef71f11d79d6fbb82adc77447f9b3526a29858f2aa3c9e51ee9098add2c7
3
+ metadata.gz: 36566e2f3d8ca8535569ec2766183295a6b17f3af67123a6442242881b004ef8
4
+ data.tar.gz: d38059a39ce762cb043c68374829050b6abd4596fc900f68d24120e843efa9db
5
5
  SHA512:
6
- metadata.gz: 91814f767f74c673681a2215f82a302f3aab5ced6ae7197720e474b8c1ad6846eb10001580c1f11d5b62bd0ed7563422fd71f0fb5728eb0b3fcb4ebd36d2038c
7
- data.tar.gz: 74e1c7ed34b4f212ca1ffd5881da3e9f689c3934d0f3680da2396d7444b5a214a610ccc779c1ef59713116f0a8240491dc60f32b5d5e30de5092ddc41b8c9f18
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 `#__polyphony_read_method__` method for read method detection
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.67)
4
+ polyphony (0.71)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -75,4 +75,4 @@ DEPENDENCIES
75
75
  simplecov (= 0.17.1)
76
76
 
77
77
  BUNDLED WITH
78
- 2.2.3
78
+ 2.2.26
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
- sleep 3
16
- socket = UNIXSocket.new(UNIX_SOCKET_PATH)
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
- status = socket.gets
24
- puts status
107
+ info = socket.gets.chomp
108
+ display_info(info)
25
109
 
26
- STDOUT << "> "
27
- cmd = STDIN.gets
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.currently_polling = 0;
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 & IOSQE_IO_LINK;
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"));
@@ -157,6 +157,8 @@ VALUE Backend_post_fork(VALUE self) {
157
157
  ev_loop_destroy(backend->ev_loop);
158
158
  backend->ev_loop = EV_DEFAULT;
159
159
 
160
+ backend_base_reset(&backend->base);
161
+
160
162
  return self;
161
163
  }
162
164
 
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/server'
128
- Polyphony::DebugServer.start(debug_socket_path)
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