polyphony 0.47.5 → 0.49.2
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 +25 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/TODO.md +34 -17
- data/examples/io/tcp_proxy.rb +32 -0
- data/examples/performance/line_splitting.rb +34 -0
- data/examples/performance/loop.rb +32 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -2
- data/ext/polyphony/backend_common.h +2 -2
- data/ext/polyphony/backend_io_uring.c +29 -68
- data/ext/polyphony/backend_libev.c +18 -59
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +5 -4
- data/ext/polyphony/queue.c +2 -2
- data/ext/polyphony/thread.c +9 -28
- data/lib/polyphony.rb +2 -1
- data/lib/polyphony/adapters/postgres.rb +3 -3
- data/lib/polyphony/adapters/process.rb +2 -0
- data/lib/polyphony/core/global_api.rb +14 -2
- data/lib/polyphony/core/thread_pool.rb +3 -1
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/core/timer.rb +72 -0
- data/lib/polyphony/extensions/fiber.rb +28 -13
- data/lib/polyphony/extensions/io.rb +8 -14
- data/lib/polyphony/extensions/openssl.rb +4 -4
- data/lib/polyphony/extensions/socket.rb +5 -1
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/net.rb +3 -6
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/helper.rb +2 -2
- data/test/test_backend.rb +26 -1
- data/test/test_fiber.rb +79 -1
- data/test/test_global_api.rb +30 -0
- data/test/test_io.rb +26 -0
- data/test/test_signal.rb +1 -2
- data/test/test_socket.rb +5 -5
- data/test/test_supervise.rb +1 -1
- data/test/test_timer.rb +124 -0
- metadata +8 -4
- data/ext/polyphony/backend.h +0 -26
@@ -34,9 +34,18 @@ module Polyphony
|
|
34
34
|
schedule Polyphony::Cancel.new
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
37
|
+
def graceful_shutdown=(graceful)
|
38
|
+
@graceful_shutdown = graceful
|
39
|
+
end
|
40
|
+
|
41
|
+
def graceful_shutdown?
|
42
|
+
@graceful_shutdown
|
43
|
+
end
|
44
|
+
|
45
|
+
def terminate(graceful = false)
|
38
46
|
return if @running == false
|
39
47
|
|
48
|
+
@graceful_shutdown = graceful
|
40
49
|
schedule Polyphony::Terminate.new
|
41
50
|
end
|
42
51
|
|
@@ -66,7 +75,9 @@ module Polyphony
|
|
66
75
|
@on_child_done = proc do |fiber, result|
|
67
76
|
self << fiber unless result.is_a?(Exception)
|
68
77
|
end
|
69
|
-
|
78
|
+
while true
|
79
|
+
supervise_perform(opts)
|
80
|
+
end
|
70
81
|
rescue Polyphony::MoveOn
|
71
82
|
# generated in #supervise_perform to stop supervisor
|
72
83
|
ensure
|
@@ -210,11 +221,14 @@ module Polyphony
|
|
210
221
|
@on_child_done&.(child_fiber, result)
|
211
222
|
end
|
212
223
|
|
213
|
-
def terminate_all_children
|
224
|
+
def terminate_all_children(graceful = false)
|
214
225
|
return unless @children
|
215
226
|
|
216
227
|
e = Polyphony::Terminate.new
|
217
|
-
@children.each_key
|
228
|
+
@children.each_key do |c|
|
229
|
+
c.graceful_shutdown = true if graceful
|
230
|
+
c.raise e
|
231
|
+
end
|
218
232
|
end
|
219
233
|
|
220
234
|
def await_all_children
|
@@ -230,9 +244,13 @@ module Polyphony
|
|
230
244
|
results.values
|
231
245
|
end
|
232
246
|
|
233
|
-
def shutdown_all_children
|
234
|
-
|
235
|
-
|
247
|
+
def shutdown_all_children(graceful = false)
|
248
|
+
return unless @children
|
249
|
+
|
250
|
+
@children.keys.each do |c|
|
251
|
+
c.terminate(graceful)
|
252
|
+
c.await
|
253
|
+
end
|
236
254
|
end
|
237
255
|
end
|
238
256
|
|
@@ -305,13 +323,10 @@ module Polyphony
|
|
305
323
|
# the children are shut down, it is returned along with the uncaught_exception
|
306
324
|
# flag set. Otherwise, it returns the given arguments.
|
307
325
|
def finalize_children(result, uncaught_exception)
|
308
|
-
|
309
|
-
shutdown_all_children
|
310
|
-
rescue Exception => e
|
311
|
-
result = e
|
312
|
-
uncaught_exception = true
|
313
|
-
end
|
326
|
+
shutdown_all_children
|
314
327
|
[result, uncaught_exception]
|
328
|
+
rescue Exception => e
|
329
|
+
[e, true]
|
315
330
|
end
|
316
331
|
|
317
332
|
def inform_dependants(result, uncaught_exception)
|
@@ -119,18 +119,11 @@ class ::IO
|
|
119
119
|
end
|
120
120
|
|
121
121
|
alias_method :orig_readpartial, :read
|
122
|
-
def readpartial(len, str =
|
123
|
-
|
124
|
-
result = Thread.current.backend.read(self, @read_buffer, len, false)
|
122
|
+
def readpartial(len, str = +'')
|
123
|
+
result = Thread.current.backend.read(self, str, len, false)
|
125
124
|
raise EOFError unless result
|
126
125
|
|
127
|
-
|
128
|
-
str << @read_buffer
|
129
|
-
else
|
130
|
-
str = @read_buffer
|
131
|
-
end
|
132
|
-
@read_buffer = +''
|
133
|
-
str
|
126
|
+
result
|
134
127
|
end
|
135
128
|
|
136
129
|
alias_method :orig_write, :write
|
@@ -154,15 +147,16 @@ class ::IO
|
|
154
147
|
|
155
148
|
@read_buffer ||= +''
|
156
149
|
|
157
|
-
|
150
|
+
while true
|
158
151
|
idx = @read_buffer.index(sep)
|
159
152
|
return @read_buffer.slice!(0, idx + sep_size) if idx
|
160
153
|
|
161
|
-
data = readpartial(8192)
|
154
|
+
data = readpartial(8192, +'')
|
155
|
+
return nil unless data
|
162
156
|
@read_buffer << data
|
163
|
-
rescue EOFError
|
164
|
-
return nil
|
165
157
|
end
|
158
|
+
rescue EOFError
|
159
|
+
return nil
|
166
160
|
end
|
167
161
|
|
168
162
|
# def print(*args)
|
@@ -25,7 +25,7 @@ class ::OpenSSL::SSL::SSLSocket
|
|
25
25
|
|
26
26
|
alias_method :orig_accept, :accept
|
27
27
|
def accept
|
28
|
-
|
28
|
+
while true
|
29
29
|
result = accept_nonblock(exception: false)
|
30
30
|
case result
|
31
31
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
@@ -37,14 +37,14 @@ class ::OpenSSL::SSL::SSLSocket
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def accept_loop
|
40
|
-
|
40
|
+
while true
|
41
41
|
yield accept
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
alias_method :orig_sysread, :sysread
|
46
46
|
def sysread(maxlen, buf = +'')
|
47
|
-
|
47
|
+
while true
|
48
48
|
case (result = read_nonblock(maxlen, buf, exception: false))
|
49
49
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
50
50
|
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
@@ -55,7 +55,7 @@ class ::OpenSSL::SSL::SSLSocket
|
|
55
55
|
|
56
56
|
alias_method :orig_syswrite, :syswrite
|
57
57
|
def syswrite(buf)
|
58
|
-
|
58
|
+
while true
|
59
59
|
case (result = write_nonblock(buf, exception: false))
|
60
60
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
61
61
|
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
@@ -11,6 +11,10 @@ class ::Socket
|
|
11
11
|
Thread.current.backend.accept(self, TCPSocket)
|
12
12
|
end
|
13
13
|
|
14
|
+
def accept_loop(&block)
|
15
|
+
Thread.current.backend.accept_loop(self, TCPSocket, &block)
|
16
|
+
end
|
17
|
+
|
14
18
|
NO_EXCEPTION = { exception: false }.freeze
|
15
19
|
|
16
20
|
def connect(addr)
|
@@ -29,7 +33,7 @@ class ::Socket
|
|
29
33
|
|
30
34
|
def recvfrom(maxlen, flags = 0)
|
31
35
|
@read_buffer ||= +''
|
32
|
-
|
36
|
+
while true
|
33
37
|
result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
|
34
38
|
case result
|
35
39
|
when nil then raise IOError
|
@@ -41,8 +41,7 @@ class ::Thread
|
|
41
41
|
|
42
42
|
def finalize(result)
|
43
43
|
unless Fiber.current.children.empty?
|
44
|
-
Fiber.current.
|
45
|
-
Fiber.current.await_all_children
|
44
|
+
Fiber.current.shutdown_all_children
|
46
45
|
end
|
47
46
|
@finalization_mutex.synchronize do
|
48
47
|
@terminated = true
|
data/lib/polyphony/net.rb
CHANGED
@@ -8,10 +8,7 @@ module Polyphony
|
|
8
8
|
module Net
|
9
9
|
class << self
|
10
10
|
def tcp_connect(host, port, opts = {})
|
11
|
-
socket =
|
12
|
-
addr = ::Socket.sockaddr_in(port, host)
|
13
|
-
s.connect(addr)
|
14
|
-
end
|
11
|
+
socket = TCPSocket.new(host, port)
|
15
12
|
if opts[:secure_context] || opts[:secure]
|
16
13
|
secure_socket(socket, opts[:secure_context], opts.merge(host: host))
|
17
14
|
else
|
@@ -23,7 +20,7 @@ module Polyphony
|
|
23
20
|
host ||= '0.0.0.0'
|
24
21
|
raise 'Port number not specified' unless port
|
25
22
|
|
26
|
-
socket =
|
23
|
+
socket = listening_socket_from_options(host, port, opts)
|
27
24
|
if opts[:secure_context] || opts[:secure]
|
28
25
|
secure_server(socket, opts[:secure_context], opts)
|
29
26
|
else
|
@@ -31,7 +28,7 @@ module Polyphony
|
|
31
28
|
end
|
32
29
|
end
|
33
30
|
|
34
|
-
def
|
31
|
+
def listening_socket_from_options(host, port, opts)
|
35
32
|
::Socket.new(:INET, :STREAM).tap do |s|
|
36
33
|
s.reuse_addr if opts[:reuse_addr]
|
37
34
|
s.dont_linger if opts[:dont_linger]
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.licenses = ['MIT']
|
7
7
|
s.summary = 'Fine grained concurrency for Ruby'
|
8
8
|
s.author = 'Sharon Rosner'
|
9
|
-
s.email = '
|
9
|
+
s.email = 'sharon@noteflakes.com'
|
10
10
|
s.files = `git ls-files`.split
|
11
11
|
s.homepage = 'https://digital-fabric.github.io/polyphony'
|
12
12
|
s.metadata = {
|
data/test/helper.rb
CHANGED
@@ -4,6 +4,7 @@ require 'bundler/setup'
|
|
4
4
|
|
5
5
|
require_relative './coverage' if ENV['COVERAGE']
|
6
6
|
|
7
|
+
require 'httparty'
|
7
8
|
require 'polyphony'
|
8
9
|
|
9
10
|
require 'fileutils'
|
@@ -56,8 +57,7 @@ class MiniTest::Test
|
|
56
57
|
|
57
58
|
def teardown
|
58
59
|
# trace "* teardown #{self.name}"
|
59
|
-
Fiber.current.
|
60
|
-
Fiber.current.await_all_children
|
60
|
+
Fiber.current.shutdown_all_children
|
61
61
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
62
62
|
rescue => e
|
63
63
|
puts e
|
data/test/test_backend.rb
CHANGED
@@ -100,12 +100,37 @@ class BackendTest < MiniTest::Test
|
|
100
100
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
101
101
|
end
|
102
102
|
|
103
|
+
def test_read_loop_terminate
|
104
|
+
i, o = IO.pipe
|
105
|
+
|
106
|
+
buf = []
|
107
|
+
parent = spin do
|
108
|
+
f = spin do
|
109
|
+
buf << :ready
|
110
|
+
@backend.read_loop(i) { |d| buf << d }
|
111
|
+
buf << :done
|
112
|
+
end
|
113
|
+
suspend
|
114
|
+
end
|
115
|
+
|
116
|
+
# writing always causes snoozing
|
117
|
+
o << 'foo'
|
118
|
+
sleep 0.01
|
119
|
+
o << 'bar'
|
120
|
+
sleep 0.01
|
121
|
+
|
122
|
+
parent.stop
|
123
|
+
|
124
|
+
parent.await
|
125
|
+
assert_equal [:ready, 'foo', 'bar'], buf
|
126
|
+
end
|
127
|
+
|
103
128
|
def test_accept_loop
|
104
129
|
server = TCPServer.new('127.0.0.1', 1234)
|
105
130
|
|
106
131
|
clients = []
|
107
132
|
server_fiber = spin do
|
108
|
-
@backend.accept_loop(server) { |c| clients << c }
|
133
|
+
@backend.accept_loop(server, TCPSocket) { |c| clients << c }
|
109
134
|
end
|
110
135
|
|
111
136
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|
data/test/test_fiber.rb
CHANGED
@@ -639,7 +639,6 @@ class FiberTest < MiniTest::Test
|
|
639
639
|
i.close
|
640
640
|
f.await
|
641
641
|
rescue Exception => e
|
642
|
-
trace e
|
643
642
|
o << e.class.name
|
644
643
|
o.close
|
645
644
|
end
|
@@ -1038,3 +1037,82 @@ class RestartTest < MiniTest::Test
|
|
1038
1037
|
assert_equal [f, 'foo', 'bar', :done, f2, 'baz', 42, :done], buffer
|
1039
1038
|
end
|
1040
1039
|
end
|
1040
|
+
|
1041
|
+
class ChildrenTerminationTest < MiniTest::Test
|
1042
|
+
def test_shutdown_all_children
|
1043
|
+
f = spin do
|
1044
|
+
1000.times { spin { suspend } }
|
1045
|
+
suspend
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
snooze
|
1049
|
+
assert_equal 1000, f.children.size
|
1050
|
+
|
1051
|
+
f.shutdown_all_children
|
1052
|
+
assert_equal 0, f.children.size
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
class GracefulTerminationTest < MiniTest::Test
|
1057
|
+
def test_graceful_termination
|
1058
|
+
buffer = []
|
1059
|
+
f = spin do
|
1060
|
+
buffer << 1
|
1061
|
+
snooze
|
1062
|
+
buffer << 2
|
1063
|
+
sleep 3
|
1064
|
+
buffer << 3
|
1065
|
+
ensure
|
1066
|
+
buffer << 4 if Fiber.current.graceful_shutdown?
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
3.times { snooze }
|
1070
|
+
f.terminate(false)
|
1071
|
+
f.await
|
1072
|
+
assert_equal [1, 2], buffer
|
1073
|
+
|
1074
|
+
buffer = []
|
1075
|
+
f = spin do
|
1076
|
+
buffer << 1
|
1077
|
+
snooze
|
1078
|
+
buffer << 2
|
1079
|
+
sleep 3
|
1080
|
+
buffer << 3
|
1081
|
+
ensure
|
1082
|
+
buffer << 4 if Fiber.current.graceful_shutdown?
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
3.times { snooze }
|
1086
|
+
f.terminate(true)
|
1087
|
+
f.await
|
1088
|
+
assert_equal [1, 2, 4], buffer
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def test_graceful_child_shutdown
|
1092
|
+
buffer = []
|
1093
|
+
f0 = spin do
|
1094
|
+
f1 = spin do
|
1095
|
+
sleep
|
1096
|
+
ensure
|
1097
|
+
buffer << 1 if Fiber.current.graceful_shutdown?
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
f2 = spin do
|
1101
|
+
sleep
|
1102
|
+
ensure
|
1103
|
+
buffer << 2 if Fiber.current.graceful_shutdown?
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
sleep
|
1107
|
+
ensure
|
1108
|
+
Fiber.current.terminate_all_children(true) if Fiber.current.graceful_shutdown?
|
1109
|
+
Fiber.current.await_all_children
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
3.times { snooze }
|
1113
|
+
f0.terminate(true)
|
1114
|
+
f0.await
|
1115
|
+
|
1116
|
+
assert_equal [1, 2], buffer
|
1117
|
+
end
|
1118
|
+
end
|
data/test/test_global_api.rb
CHANGED
@@ -307,6 +307,36 @@ class SpinLoopTest < MiniTest::Test
|
|
307
307
|
f.stop
|
308
308
|
assert_in_range 1..3, counter
|
309
309
|
end
|
310
|
+
|
311
|
+
def test_spin_loop_break
|
312
|
+
i = 0
|
313
|
+
f = spin_loop do
|
314
|
+
i += 1
|
315
|
+
snooze
|
316
|
+
break if i >= 5
|
317
|
+
end
|
318
|
+
f.await
|
319
|
+
assert_equal 5, i
|
320
|
+
|
321
|
+
i = 0
|
322
|
+
f = spin_loop do
|
323
|
+
i += 1
|
324
|
+
snooze
|
325
|
+
raise StopIteration if i >= 5
|
326
|
+
end
|
327
|
+
f.await
|
328
|
+
assert_equal 5, i
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_throttled_spin_loop_break
|
332
|
+
i = 0
|
333
|
+
f = spin_loop(rate: 100) do
|
334
|
+
i += 1
|
335
|
+
break if i >= 5
|
336
|
+
end
|
337
|
+
f.await
|
338
|
+
assert_equal 5, i
|
339
|
+
end
|
310
340
|
end
|
311
341
|
|
312
342
|
class SpinScopeTest < MiniTest::Test
|
data/test/test_io.rb
CHANGED
@@ -92,6 +92,32 @@ class IOTest < MiniTest::Test
|
|
92
92
|
assert_raises(EOFError) { i.readpartial(1) }
|
93
93
|
end
|
94
94
|
|
95
|
+
def test_gets
|
96
|
+
i, o = IO.pipe
|
97
|
+
|
98
|
+
buf = []
|
99
|
+
f = spin do
|
100
|
+
while (l = i.gets)
|
101
|
+
buf << l
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
snooze
|
106
|
+
assert_equal [], buf
|
107
|
+
|
108
|
+
o << 'fab'
|
109
|
+
snooze
|
110
|
+
assert_equal [], buf
|
111
|
+
|
112
|
+
o << "ulous\n"
|
113
|
+
10.times { snooze }
|
114
|
+
assert_equal ["fabulous\n"], buf
|
115
|
+
|
116
|
+
o.close
|
117
|
+
f.await
|
118
|
+
assert_equal ["fabulous\n"], buf
|
119
|
+
end
|
120
|
+
|
95
121
|
def test_getc
|
96
122
|
i, o = IO.pipe
|
97
123
|
|