polyphony 0.69 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1074fc28d6110cf489e8dce4fcc9c9a2430b557b57a23d865d4ac35c2bb14847
4
- data.tar.gz: 4d0de60084fdc5eec62c25306e098a19a84e7d859547397eff70e673be2fab9b
3
+ metadata.gz: 65be64df5933c24a4afb38b24c139213dcc71a7fbde7689c46c862d7bb04eef3
4
+ data.tar.gz: 3feec6f48ea17b15468790c91a790438d163b1d905d5ef6c7a2ffd6d3e47bac8
5
5
  SHA512:
6
- metadata.gz: f2aa641bceb29837bedc2b8120109b05dac20afa32923b6e20bca9a3d9d6b54886ed1410915f24497a839866ff8a660573a847868747ff1c91aa4e0ba87b45c2
7
- data.tar.gz: b49b24d6ea81f8c1a95402c9024102e0f999a079566af7667ad0ae5aab9d5fef4b934ae9711e447e7a0364d533ffe9071f41ce8309be425913606f3dad1e0860
6
+ metadata.gz: 3417d863dcd7e99c3123fbc81923627a77c6a5b45ed20d6e84148eabb945ecb38f429873be01fc53d5c35f36a225045c42e51b2c36f543fb3539ebc7d91f8bcf
7
+ data.tar.gz: be39ff1ec9616998fc2a5df0bd9af368687e3424cf4b97776466e803624953440bbf75fc166cd273f8f3d4502eefee4a13283c4129c3ed02113cc2015d314428
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.70 2021-08-19
2
+
3
+ - New implementation for `#supervise`
4
+ - Reset backend state and runqueue on fork
5
+
1
6
  ## 0.69 2021-08-16
2
7
 
3
8
  - Rename `#__polyphony_read_method__` to `#__parser_read_method__`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.69)
4
+ polyphony (0.70)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/TODO.md CHANGED
@@ -26,31 +26,8 @@
26
26
 
27
27
  - Add support for `close` to io_uring backend
28
28
 
29
- - Graceful shutdown again:
30
- - What happens to children when doing a graceful shutdown?
31
- - What are the implications of passing graceful shutdown flag to children?
32
- - What about errors while doing a graceful shutdown?
33
- - What about graceful restarts?
34
- - Some interesting discussions:
35
- - https://trio.discourse.group/search?q=graceful%20shutdown
36
- - https://github.com/python-trio/trio/issues/147
37
- - https://github.com/python-trio/trio/issues/143
38
- - https://trio.discourse.group/t/graceful-shutdown/93/2
39
- - https://250bpm.com/blog:146/
40
- - https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
41
- - https://github.com/tj/go-gracefully
42
- - `Fiber#finalize_children` should pass graceful shutdown flag to children
43
- - A good use case is an HTTP server that on graceful shutdown:
44
- - stops listening
45
- - waits for all ongoing requests to finish, optionally with a timeout
46
-
47
29
  ## Roadmap for Polyphony 1.0
48
30
 
49
- - check integration with rb-inotify
50
-
51
- - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
52
- inconsistent behaviour (see supervisor example).
53
-
54
31
  - Add test that mimics the original design for Monocrono:
55
32
  - 256 fibers each waiting for a message
56
33
  - When message received do some blocking work using a `ThreadPool`
@@ -31,6 +31,24 @@ inline void backend_base_mark(struct Backend_base *base) {
31
31
  runqueue_mark(&base->parked_runqueue);
32
32
  }
33
33
 
34
+ void backend_base_reset(struct Backend_base *base) {
35
+ runqueue_finalize(&base->runqueue);
36
+ runqueue_finalize(&base->parked_runqueue);
37
+
38
+ runqueue_initialize(&base->runqueue);
39
+ runqueue_initialize(&base->parked_runqueue);
40
+
41
+ base->currently_polling = 0;
42
+ base->op_count = 0;
43
+ base->switch_count = 0;
44
+ base->poll_count = 0;
45
+ base->pending_count = 0;
46
+ base->idle_gc_period = 0;
47
+ base->idle_gc_last_time = 0;
48
+ base->idle_proc = Qnil;
49
+ base->trace_proc = Qnil;
50
+ }
51
+
34
52
  const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
35
53
 
36
54
  inline void conditional_nonblocking_poll(VALUE backend, struct Backend_base *base, VALUE current, VALUE next) {
@@ -32,6 +32,7 @@ struct Backend_base {
32
32
  void backend_base_initialize(struct Backend_base *base);
33
33
  void backend_base_finalize(struct Backend_base *base);
34
34
  void backend_base_mark(struct Backend_base *base);
35
+ void backend_base_reset(struct Backend_base *base);
35
36
  VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
36
37
  void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
37
38
  void backend_base_park_fiber(struct Backend_base *base, VALUE fiber);
@@ -106,9 +106,7 @@ VALUE Backend_post_fork(VALUE self) {
106
106
  io_uring_queue_exit(&backend->ring);
107
107
  io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
108
108
  context_store_free(&backend->store);
109
- backend->base.currently_polling = 0;
110
- backend->base.pending_count = 0;
111
- backend->pending_sqes = 0;
109
+ backend_base_reset(&backend->base);
112
110
 
113
111
  return self;
114
112
  }
@@ -157,6 +157,8 @@ VALUE Backend_post_fork(VALUE self) {
157
157
  ev_loop_destroy(backend->ev_loop);
158
158
  backend->ev_loop = EV_DEFAULT;
159
159
 
160
+ backend_base_reset(&backend->base);
161
+
160
162
  return self;
161
163
  }
162
164
 
@@ -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
@@ -80,9 +81,7 @@ module Polyphony
80
81
  # Fiber supervision
81
82
  module FiberSupervision
82
83
  def supervise(*fibers, **opts, &block)
83
- block ||= opts[:on_done] ||
84
- (opts[:on_error] && supervise_on_error_proc(opts[:on_error]))
85
- raise "No block given" unless block
84
+ block ||= supervise_opts_to_block(opts)
86
85
 
87
86
  fibers.each do |f|
88
87
  f.attach_to(self) unless f.parent == self
@@ -97,8 +96,19 @@ module Polyphony
97
96
  end
98
97
  end
99
98
 
100
- def supervise_on_error_proc(on_error)
101
- ->(f, r) { opts[:on_error].(f, r) if r.is_a?(Exception) }
99
+ def supervise_opts_to_block(opts)
100
+ block = opts[:on_done] || opts[:on_error]
101
+ return nil unless block || opts[:restart]
102
+
103
+ error_only = !!opts[:on_error]
104
+ restart_always = opts[:restart] == :always
105
+ restart_on_error = opts[:restart] == :on_error
106
+
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)
111
+ end
102
112
  end
103
113
  end
104
114
 
@@ -225,13 +235,6 @@ module Polyphony
225
235
  c.terminate(graceful)
226
236
  c.await
227
237
  end
228
- reap_dead_children
229
- end
230
-
231
- def reap_dead_children
232
- return unless @children
233
-
234
- @children.reject! { |f| f.dead? }
235
238
  end
236
239
 
237
240
  def detach
@@ -315,6 +318,7 @@ module Polyphony
315
318
  inform_monitors(result, uncaught_exception)
316
319
  @running = false
317
320
  ensure
321
+ @parent&.remove_child(self)
318
322
  # Prevent fiber from being resumed after terminating
319
323
  @thread.fiber_unschedule(self)
320
324
  Thread.current.switch_fiber
data/lib/polyphony/net.rb CHANGED
@@ -67,7 +67,6 @@ module Polyphony
67
67
  def setup_alpn(context, protocols)
68
68
  context.alpn_protocols = protocols
69
69
  context.alpn_select_cb = lambda do |peer_protocols|
70
- p alpn_select_cb: peer_protocols
71
70
  (protocols & peer_protocols).first
72
71
  end
73
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.69'
4
+ VERSION = '0.70'
5
5
  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
 
@@ -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
 
@@ -3,12 +3,6 @@
3
3
  require_relative 'helper'
4
4
 
5
5
  class SuperviseTest < MiniTest::Test
6
- def test_supervise_with_no_arguments
7
- assert_raises(RuntimeError) do
8
- supervise
9
- end
10
- end
11
-
12
6
  def test_supervise_with_block
13
7
  buffer = []
14
8
  f1 = spin(:f1) { receive }
@@ -26,79 +20,168 @@ class SuperviseTest < MiniTest::Test
26
20
  assert_equal [[f1, 'foo'], [f2, 'bar']], buffer
27
21
  end
28
22
 
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
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
104
187
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.69'
4
+ version: '0.70'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-16 00:00:00.000000000 Z
11
+ date: 2021-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler