polyphony 0.63 → 0.67

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +19 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +10 -40
  6. data/bin/pdbg +30 -0
  7. data/examples/core/await.rb +9 -1
  8. data/ext/polyphony/backend_common.c +14 -1
  9. data/ext/polyphony/backend_common.h +3 -1
  10. data/ext/polyphony/backend_io_uring.c +84 -24
  11. data/ext/polyphony/backend_io_uring_context.c +42 -0
  12. data/ext/polyphony/backend_io_uring_context.h +6 -9
  13. data/ext/polyphony/backend_libev.c +85 -39
  14. data/ext/polyphony/fiber.c +20 -0
  15. data/ext/polyphony/polyphony.c +2 -0
  16. data/ext/polyphony/polyphony.h +5 -2
  17. data/ext/polyphony/queue.c +1 -1
  18. data/ext/polyphony/runqueue.c +7 -3
  19. data/ext/polyphony/runqueue.h +4 -3
  20. data/ext/polyphony/runqueue_ring_buffer.c +25 -14
  21. data/ext/polyphony/runqueue_ring_buffer.h +2 -0
  22. data/ext/polyphony/thread.c +2 -8
  23. data/lib/polyphony.rb +6 -0
  24. data/lib/polyphony/debugger/server.rb +137 -0
  25. data/lib/polyphony/extensions/debug.rb +1 -1
  26. data/lib/polyphony/extensions/fiber.rb +40 -33
  27. data/lib/polyphony/extensions/io.rb +6 -2
  28. data/lib/polyphony/extensions/openssl.rb +8 -2
  29. data/lib/polyphony/extensions/socket.rb +15 -10
  30. data/lib/polyphony/version.rb +1 -1
  31. data/test/helper.rb +6 -4
  32. data/test/stress.rb +6 -2
  33. data/test/test_backend.rb +13 -4
  34. data/test/test_fiber.rb +33 -9
  35. data/test/test_global_api.rb +9 -4
  36. data/test/test_io.rb +2 -0
  37. data/test/test_socket.rb +14 -11
  38. data/test/test_thread.rb +3 -0
  39. data/test/test_thread_pool.rb +1 -1
  40. data/test/test_throttler.rb +2 -2
  41. data/test/test_timer.rb +5 -3
  42. 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 */
@@ -22,14 +22,12 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
22
22
  return self;
23
23
  }
24
24
 
25
- VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
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
- VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
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
@@ -10,4 +10,4 @@ module ::Kernel
10
10
  format("%p\n", args.size == 1 ? args.first : args)
11
11
  end
12
12
  end
13
- end
13
+ 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
- Fiber.current.message_on_child_termination = true
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
- Fiber.current.send [f, f.result]
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) = receive
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
- Fiber.current.message_on_child_termination = true
169
- fibers.each { |f| f.monitor }
173
+ fibers.each { |f| f.monitor(current_fiber) }
170
174
  while true
171
- (fiber, result) = receive
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
- inform_dependants(result, uncaught_exception)
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 inform_dependants(result, uncaught_exception)
347
+ def inform_monitors(result, uncaught_exception)
345
348
  if @monitors
346
349
  msg = [self, result]
347
- @monitors.each { |f| f << msg }
350
+ @monitors.each_key { |f| f.monitor_mailbox << msg }
348
351
  end
349
352
 
350
- @parent&.child_done(self, result)
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
- attr_accessor :message_on_child_termination
359
+ def monitor(fiber)
360
+ (@monitors ||= {})[fiber] = true
361
+ end
354
362
 
355
- def monitor
356
- @monitors ||= []
357
- @monitors << Fiber.current
363
+ def unmonitor(fiber)
364
+ (@monitors ||= []).delete(fiber)
358
365
  end
359
366
 
360
- def unmonitor
361
- @monitors.delete(Fiber.current) if @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 unless result
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
- result || (raise EOFError)
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 unless result
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 unless result
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)