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 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