polyphony 0.49.1 → 0.52.0
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/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +7 -68
- data/TODO.md +6 -0
- data/examples/core/forking.rb +2 -2
- data/examples/core/nested.rb +21 -0
- data/examples/core/suspend.rb +13 -0
- data/examples/core/terminate_main_fiber.rb +12 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -4
- data/ext/polyphony/backend_common.h +58 -8
- data/ext/polyphony/backend_io_uring.c +158 -35
- data/ext/polyphony/backend_libev.c +192 -25
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/extconf.rb +7 -2
- data/ext/polyphony/fiber.c +2 -1
- data/ext/polyphony/polyphony.c +94 -0
- data/ext/polyphony/polyphony.h +29 -2
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/runqueue.c +7 -1
- data/ext/polyphony/runqueue_ring_buffer.c +9 -0
- data/ext/polyphony/runqueue_ring_buffer.h +1 -0
- data/ext/polyphony/thread.c +14 -0
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/adapters/mysql2.rb +1 -1
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +4 -4
- data/lib/polyphony/core/exceptions.rb +1 -0
- data/lib/polyphony/core/global_api.rb +6 -6
- data/lib/polyphony/core/sync.rb +1 -1
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/core/timer.rb +63 -20
- data/lib/polyphony/extensions/core.rb +5 -5
- data/lib/polyphony/extensions/fiber.rb +11 -8
- data/lib/polyphony/extensions/io.rb +13 -22
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +41 -41
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +6 -5
- data/test/helper.rb +2 -3
- data/test/stress.rb +2 -0
- data/test/test_backend.rb +58 -5
- data/test/test_fiber.rb +31 -0
- data/test/test_global_api.rb +2 -2
- data/test/test_io.rb +84 -1
- data/test/test_kernel.rb +1 -1
- data/test/test_signal.rb +2 -3
- data/test/test_socket.rb +61 -0
- data/test/test_timer.rb +41 -8
- metadata +21 -60
@@ -245,8 +245,12 @@ module Polyphony
|
|
245
245
|
end
|
246
246
|
|
247
247
|
def shutdown_all_children(graceful = false)
|
248
|
-
|
249
|
-
|
248
|
+
return unless @children
|
249
|
+
|
250
|
+
@children.keys.each do |c|
|
251
|
+
c.terminate(graceful)
|
252
|
+
c.await
|
253
|
+
end
|
250
254
|
end
|
251
255
|
end
|
252
256
|
|
@@ -312,6 +316,8 @@ module Polyphony
|
|
312
316
|
@running = false
|
313
317
|
inform_dependants(result, uncaught_exception)
|
314
318
|
ensure
|
319
|
+
# Prevent fiber from being resumed after terminating
|
320
|
+
@thread.fiber_unschedule(self)
|
315
321
|
Thread.current.switch_fiber
|
316
322
|
end
|
317
323
|
|
@@ -319,13 +325,10 @@ module Polyphony
|
|
319
325
|
# the children are shut down, it is returned along with the uncaught_exception
|
320
326
|
# flag set. Otherwise, it returns the given arguments.
|
321
327
|
def finalize_children(result, uncaught_exception)
|
322
|
-
|
323
|
-
shutdown_all_children
|
324
|
-
rescue Exception => e
|
325
|
-
result = e
|
326
|
-
uncaught_exception = true
|
327
|
-
end
|
328
|
+
shutdown_all_children
|
328
329
|
[result, uncaught_exception]
|
330
|
+
rescue Exception => e
|
331
|
+
[e, true]
|
329
332
|
end
|
330
333
|
|
331
334
|
def inform_dependants(result, uncaught_exception)
|
@@ -101,7 +101,7 @@ class ::IO
|
|
101
101
|
return @read_buffer.slice!(0) if @read_buffer && !@read_buffer.empty?
|
102
102
|
|
103
103
|
@read_buffer ||= +''
|
104
|
-
|
104
|
+
Polyphony.backend_read(self, @read_buffer, 8192, false)
|
105
105
|
return @read_buffer.slice!(0) if !@read_buffer.empty?
|
106
106
|
|
107
107
|
nil
|
@@ -110,7 +110,7 @@ class ::IO
|
|
110
110
|
alias_method :orig_read, :read
|
111
111
|
def read(len = nil)
|
112
112
|
@read_buffer ||= +''
|
113
|
-
result =
|
113
|
+
result = Polyphony.backend_read(self, @read_buffer, len, true)
|
114
114
|
return nil unless result
|
115
115
|
|
116
116
|
already_read = @read_buffer
|
@@ -120,7 +120,7 @@ class ::IO
|
|
120
120
|
|
121
121
|
alias_method :orig_readpartial, :read
|
122
122
|
def readpartial(len, str = +'')
|
123
|
-
result =
|
123
|
+
result = Polyphony.backend_read(self, str, len, false)
|
124
124
|
raise EOFError unless result
|
125
125
|
|
126
126
|
result
|
@@ -128,12 +128,12 @@ class ::IO
|
|
128
128
|
|
129
129
|
alias_method :orig_write, :write
|
130
130
|
def write(str, *args)
|
131
|
-
|
131
|
+
Polyphony.backend_write(self, str, *args)
|
132
132
|
end
|
133
133
|
|
134
134
|
alias_method :orig_write_chevron, :<<
|
135
135
|
def <<(str)
|
136
|
-
|
136
|
+
Polyphony.backend_write(self, str)
|
137
137
|
self
|
138
138
|
end
|
139
139
|
|
@@ -217,30 +217,21 @@ class ::IO
|
|
217
217
|
end
|
218
218
|
|
219
219
|
def read_loop(&block)
|
220
|
-
|
220
|
+
Polyphony.backend_read_loop(self, &block)
|
221
221
|
end
|
222
222
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
# return outbuf ? readpartial(length) : readpartial(length, outbuf)
|
227
|
-
# end
|
228
|
-
|
229
|
-
# until eof?
|
230
|
-
# outbuf ||= +''
|
231
|
-
# outbuf << readpartial(8192)
|
232
|
-
# end
|
233
|
-
# outbuf
|
234
|
-
# end
|
223
|
+
def feed_loop(receiver, method = :call, &block)
|
224
|
+
Polyphony.backend_feed_loop(self, receiver, method, &block)
|
225
|
+
end
|
235
226
|
|
236
227
|
def wait_readable(timeout = nil)
|
237
228
|
if timeout
|
238
229
|
move_on_after(timeout) do
|
239
|
-
|
230
|
+
Polyphony.backend_wait_io(self, false)
|
240
231
|
self
|
241
232
|
end
|
242
233
|
else
|
243
|
-
|
234
|
+
Polyphony.backend_wait_io(self, false)
|
244
235
|
self
|
245
236
|
end
|
246
237
|
end
|
@@ -248,11 +239,11 @@ class ::IO
|
|
248
239
|
def wait_writable(timeout = nil)
|
249
240
|
if timeout
|
250
241
|
move_on_after(timeout) do
|
251
|
-
|
242
|
+
Polyphony.backend_wait_io(self, true)
|
252
243
|
self
|
253
244
|
end
|
254
245
|
else
|
255
|
-
|
246
|
+
Polyphony.backend_wait_io(self, true)
|
256
247
|
self
|
257
248
|
end
|
258
249
|
end
|
@@ -28,8 +28,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
28
28
|
while true
|
29
29
|
result = accept_nonblock(exception: false)
|
30
30
|
case result
|
31
|
-
when :wait_readable then
|
32
|
-
when :wait_writable then
|
31
|
+
when :wait_readable then Polyphony.backend_wait_io(io, false)
|
32
|
+
when :wait_writable then Polyphony.backend_wait_io(io, true)
|
33
33
|
else
|
34
34
|
return result
|
35
35
|
end
|
@@ -46,8 +46,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
46
46
|
def sysread(maxlen, buf = +'')
|
47
47
|
while true
|
48
48
|
case (result = read_nonblock(maxlen, buf, exception: false))
|
49
|
-
when :wait_readable then
|
50
|
-
when :wait_writable then
|
49
|
+
when :wait_readable then Polyphony.backend_wait_io(io, false)
|
50
|
+
when :wait_writable then Polyphony.backend_wait_io(io, true)
|
51
51
|
else return result
|
52
52
|
end
|
53
53
|
end
|
@@ -57,8 +57,8 @@ class ::OpenSSL::SSL::SSLSocket
|
|
57
57
|
def syswrite(buf)
|
58
58
|
while true
|
59
59
|
case (result = write_nonblock(buf, exception: false))
|
60
|
-
when :wait_readable then
|
61
|
-
when :wait_writable then
|
60
|
+
when :wait_readable then Polyphony.backend_wait_io(io, false)
|
61
|
+
when :wait_writable then Polyphony.backend_wait_io(io, true)
|
62
62
|
else
|
63
63
|
return result
|
64
64
|
end
|
@@ -8,57 +8,57 @@ require_relative '../core/thread_pool'
|
|
8
8
|
# Socket overrides (eventually rewritten in C)
|
9
9
|
class ::Socket
|
10
10
|
def accept
|
11
|
-
|
11
|
+
Polyphony.backend_accept(self, TCPSocket)
|
12
12
|
end
|
13
13
|
|
14
14
|
def accept_loop(&block)
|
15
|
-
|
15
|
+
Polyphony.backend_accept_loop(self, TCPSocket, &block)
|
16
16
|
end
|
17
17
|
|
18
18
|
NO_EXCEPTION = { exception: false }.freeze
|
19
19
|
|
20
20
|
def connect(addr)
|
21
21
|
addr = Addrinfo.new(addr) if addr.is_a?(String)
|
22
|
-
|
22
|
+
Polyphony.backend_connect(self, addr.ip_address, addr.ip_port)
|
23
23
|
end
|
24
24
|
|
25
25
|
def recv(maxlen, flags = 0, outbuf = nil)
|
26
|
-
|
26
|
+
Polyphony.backend_recv(self, outbuf || +'', maxlen)
|
27
27
|
end
|
28
28
|
|
29
29
|
def recv_loop(&block)
|
30
|
-
|
30
|
+
Polyphony.backend_recv_loop(self, &block)
|
31
31
|
end
|
32
32
|
alias_method :read_loop, :recv_loop
|
33
33
|
|
34
|
+
def feed_loop(receiver, method = :call, &block)
|
35
|
+
Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
|
36
|
+
end
|
37
|
+
|
34
38
|
def recvfrom(maxlen, flags = 0)
|
35
39
|
@read_buffer ||= +''
|
36
40
|
while true
|
37
41
|
result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
|
38
42
|
case result
|
39
43
|
when nil then raise IOError
|
40
|
-
when :wait_readable then
|
44
|
+
when :wait_readable then Polyphony.backend_wait_io(self, false)
|
41
45
|
else
|
42
46
|
return result
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
47
|
-
def send(mesg, flags
|
48
|
-
|
51
|
+
def send(mesg, flags)
|
52
|
+
Polyphony.backend_send(self, mesg, flags)
|
49
53
|
end
|
50
54
|
|
51
|
-
def write(
|
52
|
-
|
53
|
-
Thread.current.backend.send(self, str)
|
54
|
-
else
|
55
|
-
Thread.current.backend.send(self, str + args.join)
|
56
|
-
end
|
55
|
+
def write(*args)
|
56
|
+
Polyphony.backend_sendv(self, args, 0)
|
57
57
|
end
|
58
58
|
alias_method :<<, :write
|
59
59
|
|
60
60
|
def readpartial(maxlen, str = +'')
|
61
|
-
|
61
|
+
Polyphony.backend_recv(self, str, maxlen)
|
62
62
|
end
|
63
63
|
|
64
64
|
ZERO_LINGER = [0, 0].pack('ii').freeze
|
@@ -138,30 +138,30 @@ class ::TCPSocket
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def recv(maxlen, flags = 0, outbuf = nil)
|
141
|
-
|
141
|
+
Polyphony.backend_recv(self, outbuf || +'', maxlen)
|
142
142
|
end
|
143
143
|
|
144
144
|
def recv_loop(&block)
|
145
|
-
|
145
|
+
Polyphony.backend_recv_loop(self, &block)
|
146
146
|
end
|
147
147
|
alias_method :read_loop, :recv_loop
|
148
148
|
|
149
|
-
def
|
150
|
-
|
149
|
+
def feed_loop(receiver, method = :call, &block)
|
150
|
+
Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
|
151
151
|
end
|
152
152
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
153
|
+
def send(mesg, flags)
|
154
|
+
Polyphony.backend_send(self, mesg, flags)
|
155
|
+
end
|
156
|
+
|
157
|
+
def write(*args)
|
158
|
+
Polyphony.backend_sendv(self, args, 0)
|
159
159
|
end
|
160
160
|
alias_method :<<, :write
|
161
161
|
|
162
162
|
def readpartial(maxlen, str = nil)
|
163
163
|
@read_buffer ||= +''
|
164
|
-
result =
|
164
|
+
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
165
165
|
raise EOFError unless result
|
166
166
|
|
167
167
|
if str
|
@@ -192,12 +192,12 @@ class ::TCPServer
|
|
192
192
|
|
193
193
|
alias_method :orig_accept, :accept
|
194
194
|
def accept
|
195
|
-
|
195
|
+
Polyphony.backend_accept(@io, TCPSocket)
|
196
196
|
# @io.accept
|
197
197
|
end
|
198
198
|
|
199
199
|
def accept_loop(&block)
|
200
|
-
|
200
|
+
Polyphony.backend_accept_loop(@io, TCPSocket, &block)
|
201
201
|
end
|
202
202
|
|
203
203
|
alias_method :orig_close, :close
|
@@ -209,40 +209,40 @@ end
|
|
209
209
|
class ::UNIXServer
|
210
210
|
alias_method :orig_accept, :accept
|
211
211
|
def accept
|
212
|
-
|
212
|
+
Polyphony.backend_accept(self, UNIXSocket)
|
213
213
|
end
|
214
214
|
|
215
215
|
def accept_loop(&block)
|
216
|
-
|
216
|
+
Polyphony.backend_accept_loop(self, UNIXSocket, &block)
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
220
220
|
class ::UNIXSocket
|
221
221
|
def recv(maxlen, flags = 0, outbuf = nil)
|
222
|
-
|
222
|
+
Polyphony.backend_recv(self, outbuf || +'', maxlen)
|
223
223
|
end
|
224
224
|
|
225
225
|
def recv_loop(&block)
|
226
|
-
|
226
|
+
Polyphony.backend_recv_loop(self, &block)
|
227
227
|
end
|
228
228
|
alias_method :read_loop, :recv_loop
|
229
229
|
|
230
|
-
def
|
231
|
-
|
230
|
+
def feed_loop(receiver, method = :call, &block)
|
231
|
+
Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
|
232
232
|
end
|
233
233
|
|
234
|
-
def
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
234
|
+
def send(mesg, flags)
|
235
|
+
Polyphony.backend_send(self, mesg, flags)
|
236
|
+
end
|
237
|
+
|
238
|
+
def write(*args)
|
239
|
+
Polyphony.backend_sendv(self, args, 0)
|
240
240
|
end
|
241
241
|
alias_method :<<, :write
|
242
242
|
|
243
243
|
def readpartial(maxlen, str = nil)
|
244
244
|
@read_buffer ||= +''
|
245
|
-
result =
|
245
|
+
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
246
246
|
raise EOFError unless result
|
247
247
|
|
248
248
|
if str
|
@@ -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/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -22,12 +22,13 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.required_ruby_version = '>= 2.6'
|
23
23
|
|
24
24
|
s.add_development_dependency 'rake-compiler', '1.1.1'
|
25
|
-
s.add_development_dependency 'minitest', '5.
|
25
|
+
s.add_development_dependency 'minitest', '5.14.4'
|
26
26
|
s.add_development_dependency 'minitest-reporters', '1.4.2'
|
27
27
|
s.add_development_dependency 'simplecov', '0.17.1'
|
28
28
|
s.add_development_dependency 'rubocop', '0.85.1'
|
29
29
|
s.add_development_dependency 'pry', '0.13.1'
|
30
30
|
|
31
|
+
s.add_development_dependency 'msgpack', '1.4.2'
|
31
32
|
s.add_development_dependency 'pg', '1.1.4'
|
32
33
|
s.add_development_dependency 'redis', '4.1.0'
|
33
34
|
s.add_development_dependency 'hiredis', '0.6.3'
|
@@ -37,8 +38,8 @@ Gem::Specification.new do |s|
|
|
37
38
|
s.add_development_dependency 'sequel', '5.34.0'
|
38
39
|
s.add_development_dependency 'httparty', '0.17.1'
|
39
40
|
|
40
|
-
s.add_development_dependency 'jekyll', '~>3.8.6'
|
41
|
-
s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
|
42
|
-
s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
|
43
|
-
s.add_development_dependency 'just-the-docs', '~>0.3.0'
|
41
|
+
# s.add_development_dependency 'jekyll', '~>3.8.6'
|
42
|
+
# s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
|
43
|
+
# s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
|
44
|
+
# s.add_development_dependency 'just-the-docs', '~>0.3.0'
|
44
45
|
end
|
data/test/helper.rb
CHANGED
@@ -52,13 +52,12 @@ class MiniTest::Test
|
|
52
52
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
53
53
|
Thread.current.backend.finalize
|
54
54
|
Thread.current.backend = Polyphony::Backend.new
|
55
|
-
sleep 0
|
55
|
+
sleep 0.001
|
56
56
|
end
|
57
57
|
|
58
58
|
def teardown
|
59
59
|
# trace "* teardown #{self.name}"
|
60
|
-
Fiber.current.
|
61
|
-
Fiber.current.await_all_children
|
60
|
+
Fiber.current.shutdown_all_children
|
62
61
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
63
62
|
rescue => e
|
64
63
|
puts e
|
data/test/stress.rb
CHANGED
data/test/test_backend.rb
CHANGED
@@ -100,12 +100,40 @@ class BackendTest < MiniTest::Test
|
|
100
100
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
101
101
|
end
|
102
102
|
|
103
|
-
def
|
104
|
-
|
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
|
+
|
128
|
+
Net = Polyphony::Net
|
129
|
+
|
130
|
+
def test_accept
|
131
|
+
server = Net.listening_socket_from_options('127.0.0.1', 1234, reuse_addr: true)
|
105
132
|
|
106
133
|
clients = []
|
107
|
-
server_fiber =
|
108
|
-
@backend.
|
134
|
+
server_fiber = spin_loop do
|
135
|
+
c = @backend.accept(server, TCPSocket)
|
136
|
+
clients << c
|
109
137
|
end
|
110
138
|
|
111
139
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|
@@ -121,7 +149,32 @@ class BackendTest < MiniTest::Test
|
|
121
149
|
ensure
|
122
150
|
c1&.close
|
123
151
|
c2&.close
|
124
|
-
server_fiber
|
152
|
+
server_fiber&.stop
|
153
|
+
snooze
|
154
|
+
server&.close
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_accept_loop
|
158
|
+
server = Net.listening_socket_from_options('127.0.0.1', 1235, reuse_addr: true)
|
159
|
+
|
160
|
+
clients = []
|
161
|
+
server_fiber = spin do
|
162
|
+
@backend.accept_loop(server, TCPSocket) { |c| clients << c }
|
163
|
+
end
|
164
|
+
|
165
|
+
c1 = TCPSocket.new('127.0.0.1', 1235)
|
166
|
+
sleep 0.01
|
167
|
+
|
168
|
+
assert_equal 1, clients.size
|
169
|
+
|
170
|
+
c2 = TCPSocket.new('127.0.0.1', 1235)
|
171
|
+
sleep 0.01
|
172
|
+
|
173
|
+
assert_equal 2, clients.size
|
174
|
+
ensure
|
175
|
+
c1&.close
|
176
|
+
c2&.close
|
177
|
+
server_fiber&.stop
|
125
178
|
snooze
|
126
179
|
server&.close
|
127
180
|
end
|