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 +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +0 -23
- data/ext/polyphony/backend_common.c +18 -0
- data/ext/polyphony/backend_common.h +1 -0
- data/ext/polyphony/backend_io_uring.c +1 -3
- data/ext/polyphony/backend_libev.c +2 -0
- data/lib/polyphony/extensions/fiber.rb +16 -12
- data/lib/polyphony/net.rb +0 -1
- data/lib/polyphony/version.rb +1 -1
- data/test/test_fiber.rb +0 -5
- data/test/test_global_api.rb +0 -1
- data/test/test_process_supervision.rb +38 -9
- data/test/test_supervise.rb +164 -81
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65be64df5933c24a4afb38b24c139213dcc71a7fbde7689c46c862d7bb04eef3
|
4
|
+
data.tar.gz: 3feec6f48ea17b15468790c91a790438d163b1d905d5ef6c7a2ffd6d3e47bac8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3417d863dcd7e99c3123fbc81923627a77c6a5b45ed20d6e84148eabb945ecb38f429873be01fc53d5c35f36a225045c42e51b2c36f543fb3539ebc7d91f8bcf
|
7
|
+
data.tar.gz: be39ff1ec9616998fc2a5df0bd9af368687e3424cf4b97776466e803624953440bbf75fc166cd273f8f3d4502eefee4a13283c4129c3ed02113cc2015d314428
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
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
|
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
|
}
|
@@ -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
|
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
|
101
|
-
|
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
data/lib/polyphony/version.rb
CHANGED
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
|
|
data/test/test_global_api.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
69
|
+
supervisor.terminate
|
70
|
+
supervisor.await
|
42
71
|
|
43
72
|
assert_equal "foo\n", IO.read(fn)
|
44
73
|
|
data/test/test_supervise.rb
CHANGED
@@ -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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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.
|
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-
|
11
|
+
date: 2021-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|