polyphony 0.66 → 0.70

Sign up to get free protection for your applications and to get access to all the features.
@@ -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