polyphony 0.66 → 0.70

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.
@@ -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
 
@@ -23,6 +27,7 @@ module Polyphony
23
27
  end
24
28
 
25
29
  fiber = parent.spin(@tag, @caller, &@block)
30
+ @monitors&.each_key { |f| fiber.monitor(f) }
26
31
  fiber.schedule(value) unless value.nil?
27
32
  fiber
28
33
  end
@@ -75,46 +80,34 @@ module Polyphony
75
80
 
76
81
  # Fiber supervision
77
82
  module FiberSupervision
78
- def supervise(opts = {})
79
- @counter = 0
80
- @on_child_done = proc do |fiber, result|
81
- self << fiber unless result.is_a?(Exception)
83
+ def supervise(*fibers, **opts, &block)
84
+ block ||= supervise_opts_to_block(opts)
85
+
86
+ fibers.each do |f|
87
+ f.attach_to(self) unless f.parent == self
88
+ f.monitor(self)
82
89
  end
90
+
91
+ mailbox = monitor_mailbox
92
+
83
93
  while true
84
- supervise_perform(opts)
94
+ (fiber, result) = mailbox.shift
95
+ block&.call(fiber, result)
85
96
  end
86
- rescue Polyphony::MoveOn
87
- # generated in #supervise_perform to stop supervisor
88
- ensure
89
- @on_child_done = nil
90
97
  end
91
98
 
92
- def supervise_perform(opts)
93
- fiber = receive
94
- if fiber && opts[:restart]
95
- restart_fiber(fiber, opts)
96
- elsif Fiber.current.children.empty?
97
- Fiber.current.stop
98
- end
99
- rescue Polyphony::Restart
100
- restart_all_children
101
- rescue Exception => e
102
- Kernel.raise e if e.source_fiber.nil? || e.source_fiber == self
99
+ def supervise_opts_to_block(opts)
100
+ block = opts[:on_done] || opts[:on_error]
101
+ return nil unless block || opts[:restart]
103
102
 
104
- if opts[:restart]
105
- restart_fiber(e.source_fiber, opts)
106
- elsif Fiber.current.children.empty?
107
- Fiber.current.stop
108
- end
109
- end
103
+ error_only = !!opts[:on_error]
104
+ restart_always = opts[:restart] == :always
105
+ restart_on_error = opts[:restart] == :on_error
110
106
 
111
- def restart_fiber(fiber, opts)
112
- opts[:watcher]&.send [:restart, fiber]
113
- case opts[:restart]
114
- when true
115
- fiber.restart
116
- when :one_for_all
117
- @children.keys.each(&:restart)
107
+ ->(f, r) do
108
+ is_error = r.is_a?(Exception)
109
+ block.(f, r) if block && (!error_only || is_error)
110
+ f.restart if restart_always || (restart_on_error && is_error)
118
111
  end
119
112
  end
120
113
  end
@@ -124,23 +117,24 @@ module Polyphony
124
117
  def await(*fibers)
125
118
  return [] if fibers.empty?
126
119
 
127
- Fiber.current.message_on_child_termination = true
120
+ current_fiber = self.current
121
+ mailbox = current_fiber.monitor_mailbox
128
122
  results = {}
129
123
  fibers.each do |f|
130
124
  results[f] = nil
131
125
  if f.dead?
132
126
  # fiber already terminated, so queue message
133
- Fiber.current.send [f, f.result]
127
+ mailbox << [f, f.result]
134
128
  else
135
- f.monitor
129
+ f.monitor(current_fiber)
136
130
  end
137
131
  end
138
132
  exception = nil
139
133
  while !fibers.empty?
140
- (fiber, result) = receive
134
+ (fiber, result) = mailbox.shift
141
135
  next unless fibers.include?(fiber)
142
-
143
136
  fibers.delete(fiber)
137
+ current_fiber.remove_child(fiber) if fiber.parent == current_fiber
144
138
  if result.is_a?(Exception)
145
139
  exception ||= result
146
140
  fibers.each { |f| f.terminate }
@@ -148,16 +142,16 @@ module Polyphony
148
142
  results[fiber] = result
149
143
  end
150
144
  end
151
- results.values
152
- ensure
153
- Fiber.current.message_on_child_termination = false
154
145
  raise exception if exception
146
+ results.values
155
147
  end
156
148
  alias_method :join, :await
157
149
 
158
150
  def select(*fibers)
159
151
  return nil if fibers.empty?
160
152
 
153
+ current_fiber = self.current
154
+ mailbox = current_fiber.monitor_mailbox
161
155
  fibers.each do |f|
162
156
  if f.dead?
163
157
  result = f.result
@@ -165,21 +159,18 @@ module Polyphony
165
159
  end
166
160
  end
167
161
 
168
- Fiber.current.message_on_child_termination = true
169
- fibers.each { |f| f.monitor }
162
+ fibers.each { |f| f.monitor(current_fiber) }
170
163
  while true
171
- (fiber, result) = receive
164
+ (fiber, result) = mailbox.shift
172
165
  next unless fibers.include?(fiber)
173
166
 
174
- fibers.each { |f| f.unmonitor }
167
+ fibers.each { |f| f.unmonitor(current_fiber) }
175
168
  if result.is_a?(Exception)
176
169
  raise result
177
170
  else
178
171
  return [fiber, result]
179
172
  end
180
173
  end
181
- ensure
182
- Fiber.current.message_on_child_termination = false
183
174
  end
184
175
 
185
176
  # Creates and schedules with priority an out-of-band fiber that runs the
@@ -219,14 +210,6 @@ module Polyphony
219
210
  f
220
211
  end
221
212
 
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
213
  def terminate_all_children(graceful = false)
231
214
  return unless @children
232
215
 
@@ -240,13 +223,15 @@ module Polyphony
240
223
  def await_all_children
241
224
  return unless @children && !@children.empty?
242
225
 
243
- Fiber.await(*@children.keys)
226
+ Fiber.await(*@children.keys.reject { |c| c.dead? })
244
227
  end
245
228
 
246
229
  def shutdown_all_children(graceful = false)
247
230
  return unless @children
248
231
 
249
232
  @children.keys.each do |c|
233
+ next if c.dead?
234
+
250
235
  c.terminate(graceful)
251
236
  c.await
252
237
  end
@@ -258,10 +243,17 @@ module Polyphony
258
243
  @parent.add_child(self)
259
244
  end
260
245
 
261
- def attach(parent)
246
+ def attach_to(fiber)
262
247
  @parent.remove_child(self)
263
- @parent = parent
264
- @parent.add_child(self)
248
+ @parent = fiber
249
+ fiber.add_child(self)
250
+ end
251
+
252
+ def attach_and_monitor(fiber)
253
+ @parent.remove_child(self)
254
+ @parent = fiber
255
+ fiber.add_child(self)
256
+ monitor(fiber)
265
257
  end
266
258
  end
267
259
 
@@ -323,9 +315,10 @@ module Polyphony
323
315
  Thread.backend.trace(:fiber_terminate, self, result)
324
316
  @result = result
325
317
 
326
- inform_dependants(result, uncaught_exception)
318
+ inform_monitors(result, uncaught_exception)
327
319
  @running = false
328
320
  ensure
321
+ @parent&.remove_child(self)
329
322
  # Prevent fiber from being resumed after terminating
330
323
  @thread.fiber_unschedule(self)
331
324
  Thread.current.switch_fiber
@@ -341,24 +334,28 @@ module Polyphony
341
334
  [e, true]
342
335
  end
343
336
 
344
- def inform_dependants(result, uncaught_exception)
337
+ def inform_monitors(result, uncaught_exception)
345
338
  if @monitors
346
339
  msg = [self, result]
347
- @monitors.each { |f| f << msg }
340
+ @monitors.each_key { |f| f.monitor_mailbox << msg }
348
341
  end
349
342
 
350
- @parent&.child_done(self, result)
343
+ if uncaught_exception && @parent
344
+ parent_is_monitor = @monitors&.has_key?(@parent)
345
+ @parent.schedule_with_priority(result) unless parent_is_monitor
346
+ end
351
347
  end
352
348
 
353
- attr_accessor :message_on_child_termination
349
+ def monitor(fiber)
350
+ (@monitors ||= {})[fiber] = true
351
+ end
354
352
 
355
- def monitor
356
- @monitors ||= []
357
- @monitors << Fiber.current
353
+ def unmonitor(fiber)
354
+ (@monitors ||= []).delete(fiber)
358
355
  end
359
356
 
360
- def unmonitor
361
- @monitors.delete(Fiber.current) if @monitors
357
+ def monitors
358
+ @monitors&.keys || []
362
359
  end
363
360
 
364
361
  def dead?
@@ -76,7 +76,7 @@ end
76
76
 
77
77
  # IO instance method patches
78
78
  class ::IO
79
- def __polyphony_read_method__
79
+ def __parser_read_method__
80
80
  :backend_read
81
81
  end
82
82
 
@@ -160,8 +160,6 @@ class ::IO
160
160
  return @read_buffer.slice!(0, idx + sep_size) if idx
161
161
 
162
162
  result = readpartial(8192, @read_buffer, -1)
163
-
164
- #Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
165
163
  return nil unless result
166
164
  end
167
165
  rescue EOFError
@@ -5,7 +5,7 @@ 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__
8
+ def __parser_read_method__
9
9
  :readpartial
10
10
  end
11
11
 
@@ -114,6 +114,68 @@ end
114
114
 
115
115
  # OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
116
116
  class ::OpenSSL::SSL::SSLServer
117
+ attr_reader :ctx
118
+
119
+ alias_method :orig_accept, :accept
120
+ def accept
121
+ # when @ctx.servername_cb is set, we use a worker thread to run the
122
+ # ssl.accept call. We need to do this because:
123
+ # - We cannot switch fibers inside of the servername_cb proc (see
124
+ # https://github.com/ruby/openssl/issues/415)
125
+ # - We don't want to stop the world while we're busy provisioning an ACME
126
+ # certificate
127
+ if @use_accept_worker.nil?
128
+ if (@use_accept_worker = use_accept_worker_thread?)
129
+ start_accept_worker_thread
130
+ end
131
+ end
132
+
133
+ sock, = @svr.accept
134
+ begin
135
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
136
+ ssl.sync_close = true
137
+ if @use_accept_worker
138
+ @accept_worker_fiber << [ssl, Fiber.current]
139
+ receive
140
+ else
141
+ ssl.accept
142
+ end
143
+ ssl
144
+ rescue Exception => ex
145
+ if ssl
146
+ ssl.close
147
+ else
148
+ sock.close
149
+ end
150
+ raise ex
151
+ end
152
+ end
153
+
154
+ def start_accept_worker_thread
155
+ fiber = Fiber.current
156
+ @accept_worker_thread = Thread.new do
157
+ fiber << Fiber.current
158
+ loop do
159
+ socket, peer = receive
160
+ socket.accept
161
+ peer << socket
162
+ rescue => e
163
+ peer.schedule(e) if fiber
164
+ end
165
+ end
166
+ @accept_worker_fiber = receive
167
+ end
168
+
169
+ def use_accept_worker_thread?
170
+ !!@ctx.servername_cb
171
+ end
172
+
173
+ alias_method :orig_close, :close
174
+ def close
175
+ @accept_worker_thread&.kill
176
+ orig_close
177
+ end
178
+
117
179
  def accept_loop(ignore_errors = true)
118
180
  loop do
119
181
  yield accept
@@ -6,7 +6,7 @@ require_relative './io'
6
6
  require_relative '../core/thread_pool'
7
7
 
8
8
  class BasicSocket
9
- def __polyphony_read_method__
9
+ def __parser_read_method__
10
10
  :backend_recv
11
11
  end
12
12
  end
@@ -206,7 +206,7 @@ class ::TCPSocket
206
206
  # Polyphony.backend_send(self, mesg, 0)
207
207
  # end
208
208
 
209
- def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
209
+ def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
210
210
  result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
211
211
  raise EOFError if !result && raise_on_eof
212
212
  result
@@ -299,7 +299,7 @@ class ::UNIXSocket
299
299
  Polyphony.backend_send(self, mesg, 0)
300
300
  end
301
301
 
302
- def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
302
+ def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
303
303
  result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
304
304
  raise EOFError if !result && raise_on_eof
305
305
  result
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.66'
4
+ VERSION = '0.70'
5
5
  end
data/test/helper.rb CHANGED
@@ -46,12 +46,7 @@ end
46
46
  class MiniTest::Test
47
47
  def setup
48
48
  # trace "* setup #{self.name}"
49
- if Fiber.current.children.size > 0
50
- puts "Children left: #{Fiber.current.children.inspect}"
51
- exit!
52
- end
53
49
  Fiber.current.setup_main_fiber
54
- Fiber.current.instance_variable_set(:@auto_watcher, nil)
55
50
  Thread.current.backend.finalize
56
51
  Thread.current.backend = Polyphony::Backend.new
57
52
  sleep 0.001
@@ -60,6 +55,10 @@ class MiniTest::Test
60
55
  def teardown
61
56
  # trace "* teardown #{self.name}"
62
57
  Fiber.current.shutdown_all_children
58
+ if Fiber.current.children.size > 0
59
+ puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
60
+ exit!
61
+ end
63
62
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
64
63
  rescue => e
65
64
  puts e
data/test/stress.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
+ test_name = ARGV[1]
4
5
 
5
- TEST_CMD = 'ruby test/run.rb'
6
+ $test_cmd = +'ruby test/run.rb'
7
+ if test_name
8
+ $test_cmd << " --name #{test_name}"
9
+ end
6
10
 
7
11
  def run_test(count)
8
12
  puts "#{count}: running tests..."
9
13
  # sleep 1
10
- system(TEST_CMD)
14
+ system($test_cmd)
11
15
  puts
12
16
 
13
17
  return if $?.exitstatus == 0
data/test/test_fiber.rb CHANGED
@@ -46,7 +46,7 @@ class FiberTest < MiniTest::Test
46
46
  def test_await_dead_children
47
47
  f1 = spin { :foo }
48
48
  f2 = spin { :bar }
49
- 2.times { snooze }
49
+ 4.times { snooze }
50
50
 
51
51
  assert_equal [:foo, :bar], Fiber.await(f1, f2)
52
52
  end
@@ -67,10 +67,10 @@ class FiberTest < MiniTest::Test
67
67
  }
68
68
  Fiber.await(f2, f3)
69
69
  assert_equal [:foo, :bar, :baz], buffer
70
- assert_equal 0, Fiber.current.children.size
70
+ assert_equal [], Fiber.current.children
71
71
  end
72
72
 
73
- def test_await_from_multiple_fibers_with_interruption=
73
+ def test_await_from_multiple_fibers_with_interruption
74
74
  buffer = []
75
75
  f1 = spin {
76
76
  sleep 0.02
@@ -563,10 +563,10 @@ class FiberTest < MiniTest::Test
563
563
  end
564
564
 
565
565
  snooze
566
- child.monitor
566
+ child.monitor(Fiber.current)
567
567
  spin { child << :foo }
568
568
 
569
- msg = receive
569
+ msg = Fiber.current.monitor_mailbox.shift
570
570
  assert_equal [child, :foo], msg
571
571
  end
572
572
 
@@ -578,14 +578,14 @@ class FiberTest < MiniTest::Test
578
578
  end
579
579
 
580
580
  snooze
581
- child.monitor
581
+ child.monitor(Fiber.current)
582
582
  spin { child << :foo }
583
583
  snooze
584
584
 
585
- child.unmonitor
585
+ child.unmonitor(Fiber.current)
586
586
 
587
- Fiber.current << :bar
588
- msg = receive
587
+ Fiber.current.monitor_mailbox << :bar
588
+ msg = Fiber.current.monitor_mailbox.shift
589
589
  assert_equal :bar, msg
590
590
  end
591
591
 
@@ -786,7 +786,7 @@ class FiberTest < MiniTest::Test
786
786
  ], buf
787
787
  end
788
788
 
789
- def test_attach
789
+ def test_attach_to
790
790
  buf = []
791
791
  child = nil
792
792
  parent = spin(:parent) do
@@ -804,7 +804,7 @@ class FiberTest < MiniTest::Test
804
804
 
805
805
  snooze
806
806
  assert_equal parent, child.parent
807
- child.attach(new_parent)
807
+ child.attach_to(new_parent)
808
808
  assert_equal new_parent, child.parent
809
809
  parent.await
810
810
 
@@ -1218,4 +1218,23 @@ class GracefulTerminationTest < MiniTest::Test
1218
1218
 
1219
1219
  assert_equal [1, 2], buffer
1220
1220
  end
1221
+ end
1222
+
1223
+ class DebugTest < MiniTest::Test
1224
+ def test_parking
1225
+ buf = []
1226
+ f = spin do
1227
+ 3.times { |i| snooze; buf << i }
1228
+ end
1229
+ assert_nil f.__parked__?
1230
+ f.__park__
1231
+ assert_equal true, f.__parked__?
1232
+ 10.times { snooze }
1233
+ assert_equal [], buf
1234
+
1235
+ f.__unpark__
1236
+ assert_nil f.__parked__?
1237
+ 10.times { snooze }
1238
+ assert_equal [0, 1, 2], buf
1239
+ end
1221
1240
  end