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