polyphony 0.67 → 0.71

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,6 +27,7 @@ module Polyphony
27
27
  end
28
28
 
29
29
  fiber = parent.spin(@tag, @caller, &@block)
30
+ @monitors&.each_key { |f| fiber.monitor(f) }
30
31
  fiber.schedule(value) unless value.nil?
31
32
  fiber
32
33
  end
@@ -79,46 +80,34 @@ module Polyphony
79
80
 
80
81
  # Fiber supervision
81
82
  module FiberSupervision
82
- def supervise(opts = {})
83
- @counter = 0
84
- @on_child_done = proc do |fiber, result|
85
- 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)
86
89
  end
90
+
91
+ mailbox = monitor_mailbox
92
+
87
93
  while true
88
- supervise_perform(opts)
94
+ (fiber, result) = mailbox.shift
95
+ block&.call(fiber, result)
89
96
  end
90
- rescue Polyphony::MoveOn
91
- # generated in #supervise_perform to stop supervisor
92
- ensure
93
- @on_child_done = nil
94
97
  end
95
98
 
96
- def supervise_perform(opts)
97
- fiber = receive
98
- if fiber && opts[:restart]
99
- restart_fiber(fiber, opts)
100
- elsif Fiber.current.children.empty?
101
- Fiber.current.stop
102
- end
103
- rescue Polyphony::Restart
104
- restart_all_children
105
- rescue Exception => e
106
- 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]
107
102
 
108
- if opts[:restart]
109
- restart_fiber(e.source_fiber, opts)
110
- elsif Fiber.current.children.empty?
111
- Fiber.current.stop
112
- end
113
- end
103
+ error_only = !!opts[:on_error]
104
+ restart_always = opts[:restart] == :always
105
+ restart_on_error = opts[:restart] == :on_error
114
106
 
115
- def restart_fiber(fiber, opts)
116
- opts[:watcher]&.send [:restart, fiber]
117
- case opts[:restart]
118
- when true
119
- fiber.restart
120
- when :one_for_all
121
- @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)
122
111
  end
123
112
  end
124
113
  end
@@ -246,13 +235,6 @@ module Polyphony
246
235
  c.terminate(graceful)
247
236
  c.await
248
237
  end
249
- reap_dead_children
250
- end
251
-
252
- def reap_dead_children
253
- return unless @children
254
-
255
- @children.reject! { |f| f.dead? }
256
238
  end
257
239
 
258
240
  def detach
@@ -261,10 +243,17 @@ module Polyphony
261
243
  @parent.add_child(self)
262
244
  end
263
245
 
264
- def attach(parent)
246
+ def attach_to(fiber)
265
247
  @parent.remove_child(self)
266
- @parent = parent
267
- @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)
268
257
  end
269
258
  end
270
259
 
@@ -329,6 +318,7 @@ module Polyphony
329
318
  inform_monitors(result, uncaught_exception)
330
319
  @running = false
331
320
  ensure
321
+ @parent&.remove_child(self)
332
322
  # Prevent fiber from being resumed after terminating
333
323
  @thread.fiber_unschedule(self)
334
324
  Thread.current.switch_fiber
@@ -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.67'
4
+ VERSION = '0.71'
5
5
  end
data/test/helper.rb CHANGED
@@ -47,7 +47,6 @@ class MiniTest::Test
47
47
  def setup
48
48
  # trace "* setup #{self.name}"
49
49
  Fiber.current.setup_main_fiber
50
- Fiber.current.instance_variable_set(:@auto_watcher, nil)
51
50
  Thread.current.backend.finalize
52
51
  Thread.current.backend = Polyphony::Backend.new
53
52
  sleep 0.001
data/test/stress.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
4
  test_name = ARGV[1]
5
5
 
6
- $test_cmd = +'ruby test/run.rb'
6
+ $test_cmd = +'ruby test/run.rb --name test_receive_cross_thread_exception'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
data/test/test_fiber.rb CHANGED
@@ -67,8 +67,6 @@ class FiberTest < MiniTest::Test
67
67
  }
68
68
  Fiber.await(f2, f3)
69
69
  assert_equal [:foo, :bar, :baz], buffer
70
- assert_equal [f1], Fiber.current.children
71
- Fiber.current.reap_dead_children
72
70
  assert_equal [], Fiber.current.children
73
71
  end
74
72
 
@@ -93,8 +91,6 @@ class FiberTest < MiniTest::Test
93
91
  f1.stop
94
92
 
95
93
  snooze
96
- assert_equal [f1, f2, f3], Fiber.current.children
97
- Fiber.current.reap_dead_children
98
94
  assert_equal [], Fiber.current.children
99
95
  end
100
96
 
@@ -602,7 +598,6 @@ class FiberTest < MiniTest::Test
602
598
 
603
599
  f.stop
604
600
  snooze
605
- Fiber.current.reap_dead_children
606
601
  assert_equal [], Fiber.current.children
607
602
  end
608
603
 
@@ -791,7 +786,7 @@ class FiberTest < MiniTest::Test
791
786
  ], buf
792
787
  end
793
788
 
794
- def test_attach
789
+ def test_attach_to
795
790
  buf = []
796
791
  child = nil
797
792
  parent = spin(:parent) do
@@ -809,7 +804,7 @@ class FiberTest < MiniTest::Test
809
804
 
810
805
  snooze
811
806
  assert_equal parent, child.parent
812
- child.attach(new_parent)
807
+ child.attach_to(new_parent)
813
808
  assert_equal new_parent, child.parent
814
809
  parent.await
815
810
 
@@ -954,6 +949,27 @@ class MailboxTest < MiniTest::Test
954
949
  assert_equal ['foo'] * 100, messages
955
950
  end
956
951
 
952
+ def test_receive_exception
953
+ e = RuntimeError.new 'foo'
954
+ spin { Fiber.current.parent << e }
955
+ r = receive
956
+ assert_equal e, r
957
+
958
+ spin { Fiber.current.parent.schedule e }
959
+ assert_raises(RuntimeError) { receive }
960
+ end
961
+
962
+ def test_receive_cross_thread_exception
963
+ e = RuntimeError.new 'foo'
964
+ f = Fiber.current
965
+ Thread.new { f << e }
966
+ r = receive
967
+ assert_equal e, r
968
+
969
+ Thread.new { f.schedule e }
970
+ assert_raises(RuntimeError) { receive }
971
+ end
972
+
957
973
  def test_receive_all_pending
958
974
  assert_equal [], receive_all_pending
959
975
 
@@ -375,7 +375,6 @@ class SpinScopeTest < MiniTest::Test
375
375
  buffer << e.message
376
376
  end
377
377
  10.times { snooze }
378
- Fiber.current.reap_dead_children
379
378
  assert_equal 0, Fiber.current.children.size
380
379
  assert_equal ['foobar'], buffer
381
380
  end
@@ -6,7 +6,7 @@ class ProcessSupervisionTest < MiniTest::Test
6
6
  def test_process_supervisor_with_block
7
7
  i, o = IO.pipe
8
8
 
9
- f = spin do
9
+ watcher = spin do
10
10
  Polyphony.watch_process do
11
11
  i.close
12
12
  sleep 5
@@ -14,31 +14,60 @@ class ProcessSupervisionTest < MiniTest::Test
14
14
  o << 'foo'
15
15
  o.close
16
16
  end
17
- supervise(on_error: :restart)
18
17
  end
19
18
 
19
+ supervisor = spin { supervise(watcher, restart: :always) }
20
+
20
21
  sleep 0.05
21
- f.terminate
22
- f.await
22
+ supervisor.terminate
23
+ supervisor.await
23
24
 
24
25
  o.close
25
26
  msg = i.read
26
- i.close
27
27
  assert_equal 'foo', msg
28
28
  end
29
29
 
30
+ def test_process_supervisor_restart_with_block
31
+ i1, o1 = IO.pipe
32
+ i2, o2 = IO.pipe
33
+
34
+ count = 0
35
+ watcher = spin do
36
+ count += 1
37
+ Polyphony.watch_process do
38
+ i1.gets
39
+ o2.puts count
40
+ end
41
+ end
42
+
43
+ supervisor = spin { supervise(watcher, restart: :always) }
44
+
45
+ o1.puts
46
+ l = i2.gets
47
+ assert_equal "1\n", l
48
+
49
+ o1.puts
50
+ l = i2.gets
51
+ assert_equal "2\n", l
52
+
53
+ o1.puts
54
+ l = i2.gets
55
+ assert_equal "3\n", l
56
+ end
57
+
30
58
  def test_process_supervisor_with_cmd
31
59
  fn = '/tmp/test_process_supervisor_with_cmd'
32
60
  FileUtils.rm(fn) rescue nil
33
61
 
34
- f = spin do
62
+ watcher = spin do
35
63
  Polyphony.watch_process("echo foo >> #{fn}")
36
- supervise(on_error: :restart)
37
64
  end
38
65
 
66
+ supervisor = spin { supervise(watcher) }
67
+
39
68
  sleep 0.05
40
- f.terminate
41
- f.await
69
+ supervisor.terminate
70
+ supervisor.await
42
71
 
43
72
  assert_equal "foo\n", IO.read(fn)
44
73
 
@@ -2,103 +2,186 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
- # class SuperviseTest < MiniTest::Test
6
- # def test_supervise
7
- # p = spin { supervise }
8
- # snooze
9
- # f1 = p.spin { receive }
10
- # f2 = p.spin { receive }
11
-
12
- # snooze
13
- # assert_equal p.state, :waiting
14
- # f1 << 'foo'
15
- # f1.await
16
- # snooze
17
-
18
- # assert_equal :waiting, p.state
19
- # assert_equal :waiting, f2.state
20
-
21
- # f2 << 'bar'
22
- # f2.await
23
- # assert_equal :runnable, p.state
24
-
25
- # 3.times { snooze }
26
- # assert_equal :dead, p.state
27
- # end
28
-
29
- # def test_supervise_with_restart
30
- # watcher = spin { receive }
31
- # parent = spin { supervise(restart: true, watcher: watcher) }
32
- # snooze
33
-
34
- # buffer = []
35
- # f1 = parent.spin do
36
- # buffer << 'f1'
37
- # end
38
-
39
- # f1.await
40
- # assert_equal ['f1'], buffer
41
- # watcher.await
42
- # assert_equal ['f1', 'f1'], buffer
43
- # end
44
-
45
- # def test_supervise_with_restart_on_error
46
- # parent = spin { supervise(restart: true) }
47
- # snooze
48
-
49
- # buffer = []
50
- # f1 = parent.spin do
51
- # buffer << 'f1'
52
- # buffer << receive
53
- # end
54
-
55
- # snooze
56
- # assert_equal ['f1'], buffer
57
-
58
- # f1.raise 'foo'
59
-
60
- # 3.times { snooze }
61
-
62
- # assert_equal ['f1', 'f1'], buffer
63
- # assert_equal :dead, f1.state
64
-
65
- # # f1 should have been restarted by supervisor
66
- # f1 = parent.children.first
67
- # assert_kind_of Fiber, f1
68
-
69
- # f1 << 'foo'
70
- # f1.await
71
-
72
- # assert_equal ['f1', 'f1', 'foo'], buffer
73
- # end
74
-
75
- # def test_supervisor_termination
76
- # f = nil
77
- # p = spin do
78
- # f = spin { sleep 1 }
79
- # supervise
80
- # end
81
- # sleep 0.01
82
-
83
- # p.terminate
84
- # p.await
85
-
86
- # assert :dead, f.state
87
- # assert :dead, p.state
88
- # end
89
-
90
- # def test_supervisor_termination_with_restart
91
- # f = nil
92
- # p = spin do
93
- # f = spin { sleep 1 }
94
- # supervise(restart: true)
95
- # end
96
- # sleep 0.01
97
-
98
- # p.terminate
99
- # p.await
100
-
101
- # assert :dead, f.state
102
- # assert :dead, p.state
103
- # end
104
- # end
5
+ class SuperviseTest < MiniTest::Test
6
+ def test_supervise_with_block
7
+ buffer = []
8
+ f1 = spin(:f1) { receive }
9
+ f2 = spin(:f2) { receive }
10
+ supervisor = spin(:supervisor) { supervise(f1, f2) { |*args| buffer << args } }
11
+
12
+ snooze
13
+ f1 << 'foo'
14
+ f1.await
15
+ 10.times { snooze }
16
+ assert_equal [[f1, 'foo']], buffer
17
+
18
+ f2 << 'bar'
19
+ f2.await
20
+ assert_equal [[f1, 'foo'], [f2, 'bar']], buffer
21
+ end
22
+
23
+ def test_supervise_with_on_done
24
+ buffer = []
25
+ f1 = spin(:f1) { receive }
26
+ f2 = spin(:f2) { receive }
27
+ supervisor = spin(:supervisor) do
28
+ supervise(f1, f2, on_done: ->(*args) { buffer << args })
29
+ end
30
+
31
+ snooze
32
+ f1 << 'foo'
33
+ f1.await
34
+ 10.times { snooze }
35
+ assert_equal [[f1, 'foo']], buffer
36
+
37
+ f2 << 'bar'
38
+ f2.await
39
+ assert_equal [[f1, 'foo'], [f2, 'bar']], buffer
40
+ end
41
+
42
+ def test_supervise_with_on_error
43
+ buffer = []
44
+ f1 = spin(:f1) { receive }
45
+ f2 = spin(:f2) { receive }
46
+ supervisor = spin(:supervisor) do
47
+ supervise(f1, f2, on_error: ->(*args) { buffer << args })
48
+ end
49
+
50
+ snooze
51
+ f1 << 'foo'
52
+ f1.await
53
+ 10.times { snooze }
54
+ assert_equal [], buffer
55
+
56
+ e = RuntimeError.new('blah')
57
+ f2.raise(e)
58
+ 3.times { snooze }
59
+ assert_equal [[f2, e]], buffer
60
+ end
61
+
62
+ def test_supervise_with_manual_restart
63
+ buffer = []
64
+ f1 = spin(:f1) { receive }
65
+ supervisor = spin(:supervisor) do
66
+ supervise(f1) do |f, r|
67
+ buffer << [f, r]
68
+ f.restart
69
+ end
70
+ end
71
+
72
+ snooze
73
+ f1 << 'foo'
74
+ f1.await
75
+ snooze
76
+ assert_equal [[f1, 'foo']], buffer
77
+
78
+ 10.times { snooze }
79
+
80
+ assert_equal 1, supervisor.children.size
81
+ f2 = supervisor.children.first
82
+ assert f1 != f2
83
+ assert_equal :f1, f2.tag
84
+ assert_equal supervisor, f2.parent
85
+
86
+ e = RuntimeError.new('bar')
87
+ f2.raise(e)
88
+ f2.await rescue nil
89
+ 3.times { snooze }
90
+ assert_equal [[f1, 'foo'], [f2, e]], buffer
91
+
92
+ assert_equal 1, supervisor.children.size
93
+ f3 = supervisor.children.first
94
+ assert f2 != f3
95
+ assert f1 != f3
96
+ assert_equal :f1, f3.tag
97
+ assert_equal supervisor, f3.parent
98
+ end
99
+
100
+ def test_supervise_with_restart_always
101
+ buffer = []
102
+ f1 = spin(:f1) do
103
+ buffer << receive
104
+ rescue => e
105
+ buffer << e
106
+ e
107
+ end
108
+ supervisor = spin(:supervisor) { supervise(f1, restart: :always) }
109
+
110
+ snooze
111
+ f1 << 'foo'
112
+ f1.await
113
+ snooze
114
+ assert_equal ['foo'], buffer
115
+
116
+ 10.times { snooze }
117
+
118
+ assert_equal 1, supervisor.children.size
119
+ f2 = supervisor.children.first
120
+ assert f1 != f2
121
+ assert_equal :f1, f2.tag
122
+ assert_equal supervisor, f2.parent
123
+
124
+ e = RuntimeError.new('bar')
125
+ f2.raise(e)
126
+ f2.await rescue nil
127
+ 3.times { snooze }
128
+ assert_equal ['foo', e], buffer
129
+
130
+ assert_equal 1, supervisor.children.size
131
+ f3 = supervisor.children.first
132
+ assert f2 != f3
133
+ assert f1 != f3
134
+ assert_equal :f1, f3.tag
135
+ assert_equal supervisor, f3.parent
136
+ end
137
+
138
+ def test_supervise_with_restart_on_error
139
+ buffer = []
140
+ f1 = spin(:f1) do
141
+ buffer << receive
142
+ rescue => e
143
+ buffer << e
144
+ e
145
+ end
146
+ supervisor = spin(:supervisor) { supervise(f1, restart: :on_error) }
147
+
148
+ snooze
149
+ e = RuntimeError.new('bar')
150
+ f1.raise(e)
151
+ f1.await rescue nil
152
+ snooze
153
+ assert_equal [e], buffer
154
+
155
+ 10.times { snooze }
156
+
157
+ assert_equal 1, supervisor.children.size
158
+ f2 = supervisor.children.first
159
+ assert f1 != f2
160
+ assert_equal :f1, f2.tag
161
+ assert_equal supervisor, f2.parent
162
+
163
+ f2 << 'foo'
164
+ f2.await rescue nil
165
+ 3.times { snooze }
166
+ assert_equal [e, 'foo'], buffer
167
+
168
+ assert_equal 0, supervisor.children.size
169
+ end
170
+
171
+ def test_supervise_terminate
172
+ buffer = []
173
+ f1 = spin(:f1) do
174
+ buffer << receive
175
+ rescue => e
176
+ buffer << e
177
+ e
178
+ end
179
+ supervisor = spin(:supervisor) { supervise(f1, restart: :on_error) }
180
+
181
+ sleep 0.05
182
+ supervisor.terminate
183
+ supervisor.await
184
+
185
+ assert_equal [], buffer
186
+ end
187
+ end