polyphony 0.45.5 → 0.46.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 +2 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/TODO.md +4 -4
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -2
- 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 +109 -0
- data/ext/polyphony/backend_io_uring.c +884 -0
- data/ext/polyphony/backend_io_uring_context.c +73 -0
- data/ext/polyphony/backend_io_uring_context.h +52 -0
- data/ext/polyphony/{libev_backend.c → backend_libev.c} +202 -294
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/extconf.rb +31 -13
- data/ext/polyphony/fiber.c +29 -22
- 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 +5 -5
- data/ext/polyphony/polyphony.h +16 -12
- data/ext/polyphony/polyphony_ext.c +10 -4
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/thread.c +11 -9
- data/lib/polyphony/adapters/trace.rb +2 -2
- data/lib/polyphony/core/global_api.rb +1 -4
- data/lib/polyphony/extensions/debug.rb +13 -0
- data/lib/polyphony/extensions/fiber.rb +2 -2
- data/lib/polyphony/extensions/socket.rb +59 -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 +5 -2
- data/test/test_backend.rb +4 -6
- data/test/test_ext.rb +1 -2
- data/test/test_fiber.rb +22 -16
- data/test/test_global_api.rb +33 -35
- data/test/test_throttler.rb +3 -6
- data/test/test_trace.rb +7 -5
- metadata +22 -3
@@ -118,13 +118,13 @@ module Polyphony
|
|
118
118
|
|
119
119
|
ALL_FIBER_EVENTS = %i[
|
120
120
|
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
121
|
-
|
121
|
+
fiber_event_poll_enter fiber_event_poll_leave
|
122
122
|
].freeze
|
123
123
|
|
124
124
|
def event_masks(events)
|
125
125
|
events.each_with_object([[], []]) do |e, masks|
|
126
126
|
case e
|
127
|
-
when
|
127
|
+
when /^fiber_/
|
128
128
|
masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
|
129
129
|
masks[0] << :c_return unless masks[0].include?(:c_return)
|
130
130
|
else
|
@@ -107,10 +107,7 @@ module Polyphony
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def sleep_forever
|
110
|
-
Thread.current.backend.
|
111
|
-
loop { sleep 60 }
|
112
|
-
ensure
|
113
|
-
Thread.current.backend.unref
|
110
|
+
Thread.current.backend.wait_event(true)
|
114
111
|
end
|
115
112
|
|
116
113
|
def throttled_loop(rate = nil, **opts, &block)
|
@@ -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
|
|
@@ -19,16 +19,21 @@ 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
|
33
|
+
end
|
34
|
+
|
35
|
+
def recv_loop(&block)
|
36
|
+
Thread.current.backend.recv_loop(self, &block)
|
32
37
|
end
|
33
38
|
|
34
39
|
def recvfrom(maxlen, flags = 0)
|
@@ -44,6 +49,19 @@ class ::Socket
|
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
52
|
+
def send(mesg, flags = 0)
|
53
|
+
Thread.current.backend.send(self, mesg)
|
54
|
+
end
|
55
|
+
|
56
|
+
def write(str)
|
57
|
+
Thread.current.backend.send(self, str)
|
58
|
+
end
|
59
|
+
alias_method :<<, :write
|
60
|
+
|
61
|
+
def readpartial(maxlen, str = +'')
|
62
|
+
Thread.current.backend.recv(self, str, maxlen)
|
63
|
+
end
|
64
|
+
|
47
65
|
ZERO_LINGER = [0, 0].pack('ii').freeze
|
48
66
|
|
49
67
|
def dont_linger
|
@@ -120,6 +138,37 @@ class ::TCPSocket
|
|
120
138
|
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
121
139
|
end
|
122
140
|
|
141
|
+
def recv(maxlen, flags = 0, outbuf = nil)
|
142
|
+
Thread.current.backend.recv(self, buf || +'', maxlen)
|
143
|
+
end
|
144
|
+
|
145
|
+
def recv_loop(&block)
|
146
|
+
Thread.current.backend.recv_loop(self, &block)
|
147
|
+
end
|
148
|
+
|
149
|
+
def send(mesg, flags = 0)
|
150
|
+
Thread.current.backend.send(self, mesg)
|
151
|
+
end
|
152
|
+
|
153
|
+
def write(str)
|
154
|
+
Thread.current.backend.send(self, str)
|
155
|
+
end
|
156
|
+
alias_method :<<, :write
|
157
|
+
|
158
|
+
def readpartial(maxlen, str = nil)
|
159
|
+
@read_buffer ||= +''
|
160
|
+
result = Thread.current.backend.recv(self, @read_buffer, maxlen)
|
161
|
+
raise EOFError unless result
|
162
|
+
|
163
|
+
if str
|
164
|
+
str << @read_buffer
|
165
|
+
else
|
166
|
+
str = @read_buffer
|
167
|
+
end
|
168
|
+
@read_buffer = +''
|
169
|
+
str
|
170
|
+
end
|
171
|
+
|
123
172
|
def read_nonblock(len, str = nil, exception: true)
|
124
173
|
@io.read_nonblock(len, str, exception: exception)
|
125
174
|
end
|
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
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
count = ARGV[0] ? ARGV[0].to_i : 100
|
4
4
|
|
5
|
-
TEST_CMD = 'ruby test/run.rb'
|
5
|
+
TEST_CMD = 'ruby test/test_backend.rb' #'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
|
|
data/test/test_ext.rb
CHANGED
@@ -183,8 +183,7 @@ class TimeoutTest < MiniTest::Test
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def test_that_timeout_method_accepts_custom_error_class_and_message
|
186
|
-
|
187
|
-
spin { 3.times { |i| buffer << i; snooze } }
|
186
|
+
e = nil
|
188
187
|
begin
|
189
188
|
Timeout.timeout(0.05, MyTimeout, 'foo') { sleep 1 }
|
190
189
|
rescue Exception => e
|
data/test/test_fiber.rb
CHANGED
@@ -62,7 +62,7 @@ class FiberTest < MiniTest::Test
|
|
62
62
|
assert_equal 0, Fiber.current.children.size
|
63
63
|
end
|
64
64
|
|
65
|
-
def test_await_from_multiple_fibers_with_interruption
|
65
|
+
def test_await_from_multiple_fibers_with_interruption=
|
66
66
|
buffer = []
|
67
67
|
f1 = spin {
|
68
68
|
sleep 0.02
|
@@ -128,15 +128,16 @@ class FiberTest < MiniTest::Test
|
|
128
128
|
worker&.join
|
129
129
|
end
|
130
130
|
|
131
|
-
def
|
132
|
-
|
131
|
+
def test_backend_wakeup_mechanism
|
132
|
+
event = Polyphony::Event.new
|
133
|
+
|
133
134
|
t = Thread.new do
|
134
135
|
f = spin_loop { snooze }
|
135
136
|
sleep 0.001
|
136
|
-
|
137
|
+
event.signal(:foo)
|
137
138
|
end
|
138
139
|
|
139
|
-
result = move_on_after(1) {
|
140
|
+
result = move_on_after(1) { event.await }
|
140
141
|
|
141
142
|
assert_equal :foo, result
|
142
143
|
ensure
|
@@ -469,12 +470,14 @@ class FiberTest < MiniTest::Test
|
|
469
470
|
end
|
470
471
|
snooze # allow nested fiber to run before finishing
|
471
472
|
end
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
473
|
+
begin
|
474
|
+
suspend
|
475
|
+
rescue Exception => e
|
476
|
+
raised_error = e
|
477
|
+
ensure
|
478
|
+
assert raised_error
|
479
|
+
assert_equal 'foo', raised_error.message
|
480
|
+
end
|
478
481
|
end
|
479
482
|
|
480
483
|
def test_await_multiple_fibers
|
@@ -631,11 +634,12 @@ class FiberTest < MiniTest::Test
|
|
631
634
|
def test_signal_handling_int
|
632
635
|
i, o = IO.pipe
|
633
636
|
pid = Polyphony.fork do
|
634
|
-
f = spin { sleep
|
637
|
+
f = spin { sleep 3 }
|
635
638
|
begin
|
636
639
|
i.close
|
637
640
|
f.await
|
638
641
|
rescue Exception => e
|
642
|
+
trace e
|
639
643
|
o << e.class.name
|
640
644
|
o.close
|
641
645
|
end
|
@@ -653,7 +657,7 @@ class FiberTest < MiniTest::Test
|
|
653
657
|
def test_signal_handling_term
|
654
658
|
i, o = IO.pipe
|
655
659
|
pid = Polyphony.fork do
|
656
|
-
f = spin { sleep
|
660
|
+
f = spin { sleep 3 }
|
657
661
|
begin
|
658
662
|
i.close
|
659
663
|
f.await
|
@@ -662,7 +666,7 @@ class FiberTest < MiniTest::Test
|
|
662
666
|
o.close
|
663
667
|
end
|
664
668
|
end
|
665
|
-
sleep 0.
|
669
|
+
sleep 0.1
|
666
670
|
f = spin { Thread.current.backend.waitpid(pid) }
|
667
671
|
o.close
|
668
672
|
Process.kill('TERM', pid)
|
@@ -677,7 +681,7 @@ class FiberTest < MiniTest::Test
|
|
677
681
|
pid = Polyphony.fork do
|
678
682
|
i.close
|
679
683
|
spin do
|
680
|
-
sleep
|
684
|
+
sleep 3
|
681
685
|
rescue Exception => e
|
682
686
|
o << e.class.to_s
|
683
687
|
o.close
|
@@ -687,7 +691,7 @@ class FiberTest < MiniTest::Test
|
|
687
691
|
end
|
688
692
|
o.close
|
689
693
|
spin do
|
690
|
-
sleep 0.
|
694
|
+
sleep 0.1
|
691
695
|
Process.kill('TERM', pid)
|
692
696
|
end
|
693
697
|
Thread.current.backend.waitpid(pid)
|
@@ -703,6 +707,7 @@ class FiberTest < MiniTest::Test
|
|
703
707
|
assert_nil f.thread
|
704
708
|
snooze
|
705
709
|
f.setup_raw
|
710
|
+
|
706
711
|
assert_equal Thread.current, f.thread
|
707
712
|
assert_nil f.parent
|
708
713
|
|
@@ -711,6 +716,7 @@ class FiberTest < MiniTest::Test
|
|
711
716
|
f << 'bar'
|
712
717
|
snooze
|
713
718
|
assert_equal ['bar'], buffer
|
719
|
+
snooze
|
714
720
|
end
|
715
721
|
end
|
716
722
|
|