polyphony 0.45.5 → 0.47.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/.github/workflows/test.yml +2 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/TODO.md +21 -22
- data/bin/test +4 -0
- data/examples/core/enumerable.rb +64 -0
- data/examples/performance/fiber_resume.rb +43 -0
- data/examples/performance/fiber_transfer.rb +13 -4
- data/examples/performance/thread-vs-fiber/compare.rb +59 -0
- data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
- data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
- data/examples/performance/thread_switch.rb +44 -0
- data/ext/liburing/liburing.h +585 -0
- data/ext/liburing/liburing/README.md +4 -0
- data/ext/liburing/liburing/barrier.h +73 -0
- data/ext/liburing/liburing/compat.h +15 -0
- data/ext/liburing/liburing/io_uring.h +343 -0
- data/ext/liburing/queue.c +333 -0
- data/ext/liburing/register.c +187 -0
- data/ext/liburing/setup.c +210 -0
- data/ext/liburing/syscall.c +54 -0
- data/ext/liburing/syscall.h +18 -0
- data/ext/polyphony/backend.h +0 -14
- data/ext/polyphony/backend_common.h +129 -0
- data/ext/polyphony/backend_io_uring.c +995 -0
- data/ext/polyphony/backend_io_uring_context.c +74 -0
- data/ext/polyphony/backend_io_uring_context.h +53 -0
- data/ext/polyphony/{libev_backend.c → backend_libev.c} +304 -294
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/extconf.rb +31 -13
- data/ext/polyphony/fiber.c +35 -24
- data/ext/polyphony/libev.c +4 -0
- data/ext/polyphony/libev.h +8 -2
- data/ext/polyphony/liburing.c +8 -0
- data/ext/polyphony/playground.c +51 -0
- data/ext/polyphony/polyphony.c +8 -5
- data/ext/polyphony/polyphony.h +23 -19
- data/ext/polyphony/polyphony_ext.c +10 -4
- data/ext/polyphony/queue.c +100 -35
- data/ext/polyphony/thread.c +10 -10
- data/lib/polyphony/adapters/trace.rb +2 -2
- data/lib/polyphony/core/exceptions.rb +0 -4
- data/lib/polyphony/core/global_api.rb +45 -21
- data/lib/polyphony/core/resource_pool.rb +12 -1
- data/lib/polyphony/extensions/core.rb +9 -15
- data/lib/polyphony/extensions/debug.rb +13 -0
- data/lib/polyphony/extensions/fiber.rb +8 -4
- data/lib/polyphony/extensions/openssl.rb +6 -0
- data/lib/polyphony/extensions/socket.rb +73 -10
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +36 -4
- data/test/io_uring_test.rb +55 -0
- data/test/stress.rb +4 -1
- data/test/test_backend.rb +63 -6
- data/test/test_ext.rb +1 -2
- data/test/test_fiber.rb +55 -20
- data/test/test_global_api.rb +107 -35
- data/test/test_queue.rb +117 -0
- data/test/test_resource_pool.rb +21 -0
- data/test/test_socket.rb +2 -2
- data/test/test_throttler.rb +3 -6
- data/test/test_trace.rb +7 -5
- metadata +28 -3
@@ -12,7 +12,7 @@ class ::Exception
|
|
12
12
|
attr_accessor :__disable_sanitized_backtrace__
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :source_fiber
|
15
|
+
attr_accessor :source_fiber, :__raising_fiber__
|
16
16
|
|
17
17
|
alias_method :orig_initialize, :initialize
|
18
18
|
def initialize(*args)
|
@@ -22,8 +22,8 @@ class ::Exception
|
|
22
22
|
|
23
23
|
alias_method :orig_backtrace, :backtrace
|
24
24
|
def backtrace
|
25
|
-
unless @
|
26
|
-
@
|
25
|
+
unless @backtrace_called
|
26
|
+
@backtrace_called = true
|
27
27
|
return orig_backtrace
|
28
28
|
end
|
29
29
|
|
@@ -31,12 +31,10 @@ class ::Exception
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def sanitized_backtrace
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
sanitize(orig_backtrace)
|
39
|
-
end
|
34
|
+
return sanitize(orig_backtrace) unless @__raising_fiber__
|
35
|
+
|
36
|
+
backtrace = orig_backtrace || []
|
37
|
+
sanitize(backtrace + @__raising_fiber__.caller)
|
40
38
|
end
|
41
39
|
|
42
40
|
POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
|
@@ -164,11 +162,7 @@ end
|
|
164
162
|
|
165
163
|
# Override Timeout to use cancel scope
|
166
164
|
module ::Timeout
|
167
|
-
def self.timeout(sec, klass =
|
168
|
-
cancel_after(sec, &block)
|
169
|
-
rescue Polyphony::Cancel => e
|
170
|
-
error = klass ? klass.new(message) : ::Timeout::Error.new
|
171
|
-
error.set_backtrace(e.backtrace)
|
172
|
-
raise error
|
165
|
+
def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
|
166
|
+
cancel_after(sec, with_exception: [klass, message], &block)
|
173
167
|
end
|
174
168
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ::Kernel
|
2
|
+
def trace(*args)
|
3
|
+
STDOUT.orig_write(format_trace(args))
|
4
|
+
end
|
5
|
+
|
6
|
+
def format_trace(args)
|
7
|
+
if args.size > 1 && args.first.is_a?(String)
|
8
|
+
format("%s: %p\n", args.shift, args.size == 1 ? args.first : args)
|
9
|
+
else
|
10
|
+
format("%p\n", args.size == 1 ? args.first : args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -175,9 +175,9 @@ module Polyphony
|
|
175
175
|
f = Fiber.new do
|
176
176
|
block.call
|
177
177
|
rescue Exception => e
|
178
|
-
Thread.current.
|
178
|
+
Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
|
179
179
|
end
|
180
|
-
Thread.current.
|
180
|
+
Thread.current.schedule_and_wakeup(f, nil)
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
@@ -328,14 +328,18 @@ class ::Fiber
|
|
328
328
|
extend Polyphony::FiberControlClassMethods
|
329
329
|
|
330
330
|
attr_accessor :tag, :thread, :parent
|
331
|
-
attr_reader :result
|
331
|
+
attr_reader :result
|
332
332
|
|
333
333
|
def running?
|
334
334
|
@running
|
335
335
|
end
|
336
336
|
|
337
337
|
def inspect
|
338
|
-
|
338
|
+
if @tag
|
339
|
+
"#<Fiber #{tag}:#{object_id} #{location} (#{state})>"
|
340
|
+
else
|
341
|
+
"#<Fiber:#{object_id} #{location} (#{state})>"
|
342
|
+
end
|
339
343
|
end
|
340
344
|
alias_method :to_s, :inspect
|
341
345
|
|
@@ -19,18 +19,24 @@ class ::Socket
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def recv(maxlen, flags = 0, outbuf = nil)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
22
|
+
Thread.current.backend.recv(self, buf || +'', maxlen)
|
23
|
+
# outbuf ||= +''
|
24
|
+
# loop do
|
25
|
+
# result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
|
26
|
+
# case result
|
27
|
+
# when nil then raise IOError
|
28
|
+
# when :wait_readable then Thread.current.backend.wait_io(self, false)
|
29
|
+
# else
|
30
|
+
# return result
|
31
|
+
# end
|
32
|
+
# end
|
32
33
|
end
|
33
34
|
|
35
|
+
def recv_loop(&block)
|
36
|
+
Thread.current.backend.recv_loop(self, &block)
|
37
|
+
end
|
38
|
+
alias_method :read_loop, :recv_loop
|
39
|
+
|
34
40
|
def recvfrom(maxlen, flags = 0)
|
35
41
|
@read_buffer ||= +''
|
36
42
|
loop do
|
@@ -44,6 +50,23 @@ class ::Socket
|
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
53
|
+
def send(mesg, flags = 0)
|
54
|
+
Thread.current.backend.send(self, mesg)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(str, *args)
|
58
|
+
if args.empty?
|
59
|
+
Thread.current.backend.send(self, str)
|
60
|
+
else
|
61
|
+
Thread.current.backend.send(self, str + args.join)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
alias_method :<<, :write
|
65
|
+
|
66
|
+
def readpartial(maxlen, str = +'')
|
67
|
+
Thread.current.backend.recv(self, str, maxlen)
|
68
|
+
end
|
69
|
+
|
47
70
|
ZERO_LINGER = [0, 0].pack('ii').freeze
|
48
71
|
|
49
72
|
def dont_linger
|
@@ -120,6 +143,42 @@ class ::TCPSocket
|
|
120
143
|
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
121
144
|
end
|
122
145
|
|
146
|
+
def recv(maxlen, flags = 0, outbuf = nil)
|
147
|
+
Thread.current.backend.recv(self, buf || +'', maxlen)
|
148
|
+
end
|
149
|
+
|
150
|
+
def recv_loop(&block)
|
151
|
+
Thread.current.backend.recv_loop(self, &block)
|
152
|
+
end
|
153
|
+
alias_method :read_loop, :recv_loop
|
154
|
+
|
155
|
+
def send(mesg, flags = 0)
|
156
|
+
Thread.current.backend.send(self, mesg)
|
157
|
+
end
|
158
|
+
|
159
|
+
def write(str, *args)
|
160
|
+
if args.empty?
|
161
|
+
Thread.current.backend.send(self, str)
|
162
|
+
else
|
163
|
+
Thread.current.backend.send(self, str + args.join)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
alias_method :<<, :write
|
167
|
+
|
168
|
+
def readpartial(maxlen, str = nil)
|
169
|
+
@read_buffer ||= +''
|
170
|
+
result = Thread.current.backend.recv(self, @read_buffer, maxlen)
|
171
|
+
raise EOFError unless result
|
172
|
+
|
173
|
+
if str
|
174
|
+
str << @read_buffer
|
175
|
+
else
|
176
|
+
str = @read_buffer
|
177
|
+
end
|
178
|
+
@read_buffer = +''
|
179
|
+
str
|
180
|
+
end
|
181
|
+
|
123
182
|
def read_nonblock(len, str = nil, exception: true)
|
124
183
|
@io.read_nonblock(len, str, exception: exception)
|
125
184
|
end
|
@@ -142,6 +201,10 @@ class ::TCPServer
|
|
142
201
|
@io.accept
|
143
202
|
end
|
144
203
|
|
204
|
+
def accept_loop(&block)
|
205
|
+
Thread.current.backend.accept_loop(@io, &block)
|
206
|
+
end
|
207
|
+
|
145
208
|
alias_method :orig_close, :close
|
146
209
|
def close
|
147
210
|
@io.close
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -22,29 +22,52 @@ class ::Fiber
|
|
22
22
|
attr_writer :auto_watcher
|
23
23
|
end
|
24
24
|
|
25
|
+
module ::Kernel
|
26
|
+
def trace(*args)
|
27
|
+
STDOUT.orig_write(format_trace(args))
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_trace(args)
|
31
|
+
if args.first.is_a?(String)
|
32
|
+
if args.size > 1
|
33
|
+
format("%s: %p\n", args.shift, args)
|
34
|
+
else
|
35
|
+
format("%s\n", args.first)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
format("%p\n", args.size == 1 ? args.first : args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
25
43
|
class MiniTest::Test
|
26
44
|
def setup
|
27
|
-
#
|
45
|
+
# trace "* setup #{self.name}"
|
28
46
|
if Fiber.current.children.size > 0
|
29
47
|
puts "Children left: #{Fiber.current.children.inspect}"
|
30
48
|
exit!
|
31
49
|
end
|
32
50
|
Fiber.current.setup_main_fiber
|
33
51
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
52
|
+
Thread.current.backend.finalize
|
34
53
|
Thread.current.backend = Polyphony::Backend.new
|
35
|
-
sleep 0
|
54
|
+
sleep 0
|
36
55
|
end
|
37
56
|
|
38
57
|
def teardown
|
39
|
-
#
|
58
|
+
# trace "* teardown #{self.name}"
|
40
59
|
Fiber.current.terminate_all_children
|
41
60
|
Fiber.current.await_all_children
|
42
|
-
Fiber.current.auto_watcher
|
61
|
+
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
43
62
|
rescue => e
|
44
63
|
puts e
|
45
64
|
puts e.backtrace.join("\n")
|
46
65
|
exit!
|
47
66
|
end
|
67
|
+
|
68
|
+
def fiber_tree(fiber)
|
69
|
+
{ fiber: fiber, children: fiber.children.map { |f| fiber_tree(f) } }
|
70
|
+
end
|
48
71
|
end
|
49
72
|
|
50
73
|
module Kernel
|
@@ -54,3 +77,12 @@ module Kernel
|
|
54
77
|
e
|
55
78
|
end
|
56
79
|
end
|
80
|
+
|
81
|
+
module Minitest::Assertions
|
82
|
+
def assert_in_range exp_range, act
|
83
|
+
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
84
|
+
assert exp_range.include?(act), msg
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
puts "Polyphony backend: #{Thread.current.backend.kind}"
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
6
|
+
|
7
|
+
module ::Kernel
|
8
|
+
def trace(*args)
|
9
|
+
STDOUT.orig_write(format_trace(args))
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_trace(args)
|
13
|
+
if args.first.is_a?(String)
|
14
|
+
if args.size > 1
|
15
|
+
format("%s: %p\n", args.shift, args)
|
16
|
+
else
|
17
|
+
format("%s\n", args.first)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
format("%p\n", args.size == 1 ? args.first : args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
i, o = IO.pipe
|
26
|
+
|
27
|
+
buf = []
|
28
|
+
f = spin do
|
29
|
+
buf << :ready
|
30
|
+
# loop do
|
31
|
+
# s = Thread.current.backend.read(i, +'', 6, false)
|
32
|
+
# trace read_result: s
|
33
|
+
# break if s.nil?
|
34
|
+
# buf << s
|
35
|
+
# rescue Exception => e
|
36
|
+
# trace exception: e
|
37
|
+
# raise e
|
38
|
+
# end
|
39
|
+
Thread.current.backend.read_loop(i) { |d| buf << d }
|
40
|
+
buf << :done
|
41
|
+
end
|
42
|
+
|
43
|
+
# writing always causes snoozing
|
44
|
+
o << 'foo'
|
45
|
+
o << 'bar'
|
46
|
+
trace '...closing'
|
47
|
+
o.close
|
48
|
+
trace '...closed'
|
49
|
+
|
50
|
+
f.await
|
51
|
+
|
52
|
+
raise "Bad result: #{buf.inspect}" unless buf == [:ready, 'foo', 'bar', :done]
|
53
|
+
|
54
|
+
puts '-' * 80
|
55
|
+
p buf
|
data/test/stress.rb
CHANGED
@@ -6,6 +6,7 @@ TEST_CMD = 'ruby test/run.rb'
|
|
6
6
|
|
7
7
|
def run_test(count)
|
8
8
|
puts "#{count}: running tests..."
|
9
|
+
# sleep 1
|
9
10
|
system(TEST_CMD)
|
10
11
|
return if $?.exitstatus == 0
|
11
12
|
|
@@ -15,7 +16,9 @@ end
|
|
15
16
|
|
16
17
|
trap('INT') { exit! }
|
17
18
|
t0 = Time.now
|
18
|
-
count.times
|
19
|
+
count.times do |i|
|
20
|
+
run_test(i + 1)
|
21
|
+
end
|
19
22
|
elapsed = Time.now - t0
|
20
23
|
puts format(
|
21
24
|
"Successfully ran %d tests in %f seconds (%f per test)",
|
data/test/test_backend.rb
CHANGED
@@ -85,7 +85,7 @@ class BackendTest < MiniTest::Test
|
|
85
85
|
i, o = IO.pipe
|
86
86
|
|
87
87
|
buf = []
|
88
|
-
spin do
|
88
|
+
f = spin do
|
89
89
|
buf << :ready
|
90
90
|
@backend.read_loop(i) { |d| buf << d }
|
91
91
|
buf << :done
|
@@ -96,9 +96,7 @@ class BackendTest < MiniTest::Test
|
|
96
96
|
o << 'bar'
|
97
97
|
o.close
|
98
98
|
|
99
|
-
|
100
|
-
6.times { snooze }
|
101
|
-
|
99
|
+
f.await
|
102
100
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
103
101
|
end
|
104
102
|
|
@@ -111,12 +109,12 @@ class BackendTest < MiniTest::Test
|
|
111
109
|
end
|
112
110
|
|
113
111
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|
114
|
-
|
112
|
+
sleep 0.01
|
115
113
|
|
116
114
|
assert_equal 1, clients.size
|
117
115
|
|
118
116
|
c2 = TCPSocket.new('127.0.0.1', 1234)
|
119
|
-
|
117
|
+
sleep 0.01
|
120
118
|
|
121
119
|
assert_equal 2, clients.size
|
122
120
|
|
@@ -127,4 +125,63 @@ class BackendTest < MiniTest::Test
|
|
127
125
|
snooze
|
128
126
|
server&.close
|
129
127
|
end
|
128
|
+
|
129
|
+
def test_timer_loop
|
130
|
+
i = 0
|
131
|
+
f = spin do
|
132
|
+
@backend.timer_loop(0.01) { i += 1 }
|
133
|
+
end
|
134
|
+
@backend.sleep(0.05)
|
135
|
+
f.stop
|
136
|
+
f.await # TODO: check why this test sometimes segfaults if we don't a<wait fiber
|
137
|
+
assert_in_range 4..6, i
|
138
|
+
end
|
139
|
+
|
140
|
+
class MyTimeoutException < Exception
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_timeout
|
144
|
+
buffer = []
|
145
|
+
assert_raises(Polyphony::TimeoutException) do
|
146
|
+
@backend.timeout(0.01, Polyphony::TimeoutException) do
|
147
|
+
buffer << 1
|
148
|
+
sleep 0.02
|
149
|
+
buffer << 2
|
150
|
+
end
|
151
|
+
end
|
152
|
+
assert_equal [1], buffer
|
153
|
+
|
154
|
+
buffer = []
|
155
|
+
assert_raises(MyTimeoutException) do
|
156
|
+
@backend.timeout(0.01, MyTimeoutException) do
|
157
|
+
buffer << 1
|
158
|
+
sleep 1
|
159
|
+
buffer << 2
|
160
|
+
end
|
161
|
+
end
|
162
|
+
assert_equal [1], buffer
|
163
|
+
|
164
|
+
buffer = []
|
165
|
+
result = @backend.timeout(0.01, nil, 42) do
|
166
|
+
buffer << 1
|
167
|
+
sleep 1
|
168
|
+
buffer << 2
|
169
|
+
end
|
170
|
+
assert_equal 42, result
|
171
|
+
assert_equal [1], buffer
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_nested_timeout
|
175
|
+
buffer = []
|
176
|
+
assert_raises(MyTimeoutException) do
|
177
|
+
@backend.timeout(0.01, MyTimeoutException) do
|
178
|
+
@backend.timeout(0.02, nil) do
|
179
|
+
buffer << 1
|
180
|
+
sleep 1
|
181
|
+
buffer << 2
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
assert_equal [1], buffer
|
186
|
+
end
|
130
187
|
end
|