polyphony 0.64 → 0.68
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 +22 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -40
- data/bin/pdbg +112 -0
- data/examples/core/await.rb +9 -1
- data/ext/polyphony/backend_common.c +14 -1
- data/ext/polyphony/backend_common.h +3 -1
- data/ext/polyphony/backend_io_uring.c +85 -25
- data/ext/polyphony/backend_io_uring_context.c +42 -0
- data/ext/polyphony/backend_io_uring_context.h +6 -9
- data/ext/polyphony/backend_libev.c +85 -39
- data/ext/polyphony/fiber.c +20 -0
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +5 -2
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/runqueue.c +7 -3
- data/ext/polyphony/runqueue.h +4 -3
- data/ext/polyphony/runqueue_ring_buffer.c +25 -14
- data/ext/polyphony/runqueue_ring_buffer.h +2 -0
- data/ext/polyphony/thread.c +2 -8
- data/lib/polyphony.rb +6 -0
- data/lib/polyphony/debugger.rb +225 -0
- data/lib/polyphony/extensions/debug.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +64 -71
- data/lib/polyphony/extensions/io.rb +4 -2
- data/lib/polyphony/extensions/openssl.rb +66 -0
- data/lib/polyphony/extensions/socket.rb +8 -2
- data/lib/polyphony/net.rb +1 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +6 -5
- data/test/stress.rb +6 -2
- data/test/test_backend.rb +13 -4
- data/test/test_fiber.rb +35 -11
- data/test/test_global_api.rb +9 -4
- data/test/test_io.rb +2 -0
- data/test/test_socket.rb +14 -11
- data/test/test_supervise.rb +24 -24
- data/test/test_thread.rb +3 -0
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +2 -2
- data/test/test_timer.rb +5 -3
- metadata +5 -3
@@ -76,6 +76,10 @@ end
|
|
76
76
|
|
77
77
|
# IO instance method patches
|
78
78
|
class ::IO
|
79
|
+
def __polyphony_read_method__
|
80
|
+
:backend_read
|
81
|
+
end
|
82
|
+
|
79
83
|
# def each(sep = $/, limit = nil, chomp: nil)
|
80
84
|
# sep, limit = $/, sep if sep.is_a?(Integer)
|
81
85
|
# end
|
@@ -156,8 +160,6 @@ class ::IO
|
|
156
160
|
return @read_buffer.slice!(0, idx + sep_size) if idx
|
157
161
|
|
158
162
|
result = readpartial(8192, @read_buffer, -1)
|
159
|
-
|
160
|
-
#Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
|
161
163
|
return nil unless result
|
162
164
|
end
|
163
165
|
rescue EOFError
|
@@ -5,6 +5,10 @@ require_relative './socket'
|
|
5
5
|
|
6
6
|
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
7
7
|
class ::OpenSSL::SSL::SSLSocket
|
8
|
+
def __polyphony_read_method__
|
9
|
+
:readpartial
|
10
|
+
end
|
11
|
+
|
8
12
|
alias_method :orig_initialize, :initialize
|
9
13
|
def initialize(socket, context = nil)
|
10
14
|
socket = socket.respond_to?(:io) ? socket.io || socket : socket
|
@@ -110,6 +114,68 @@ end
|
|
110
114
|
|
111
115
|
# OpenSSL socket helper methods (to make it compatible with Socket API) and overrides
|
112
116
|
class ::OpenSSL::SSL::SSLServer
|
117
|
+
attr_reader :ctx
|
118
|
+
|
119
|
+
alias_method :orig_accept, :accept
|
120
|
+
def accept
|
121
|
+
# when @ctx.servername_cb is set, we use a worker thread to run the
|
122
|
+
# ssl.accept call. We need to do this because:
|
123
|
+
# - We cannot switch fibers inside of the servername_cb proc (see
|
124
|
+
# https://github.com/ruby/openssl/issues/415)
|
125
|
+
# - We don't want to stop the world while we're busy provisioning an ACME
|
126
|
+
# certificate
|
127
|
+
if @use_accept_worker.nil?
|
128
|
+
if (@use_accept_worker = use_accept_worker_thread?)
|
129
|
+
start_accept_worker_thread
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
sock, = @svr.accept
|
134
|
+
begin
|
135
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
|
136
|
+
ssl.sync_close = true
|
137
|
+
if @use_accept_worker
|
138
|
+
@accept_worker_fiber << [ssl, Fiber.current]
|
139
|
+
receive
|
140
|
+
else
|
141
|
+
ssl.accept
|
142
|
+
end
|
143
|
+
ssl
|
144
|
+
rescue Exception => ex
|
145
|
+
if ssl
|
146
|
+
ssl.close
|
147
|
+
else
|
148
|
+
sock.close
|
149
|
+
end
|
150
|
+
raise ex
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def start_accept_worker_thread
|
155
|
+
fiber = Fiber.current
|
156
|
+
@accept_worker_thread = Thread.new do
|
157
|
+
fiber << Fiber.current
|
158
|
+
loop do
|
159
|
+
socket, peer = receive
|
160
|
+
socket.accept
|
161
|
+
peer << socket
|
162
|
+
rescue => e
|
163
|
+
peer.schedule(e) if fiber
|
164
|
+
end
|
165
|
+
end
|
166
|
+
@accept_worker_fiber = receive
|
167
|
+
end
|
168
|
+
|
169
|
+
def use_accept_worker_thread?
|
170
|
+
!!@ctx.servername_cb
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :orig_close, :close
|
174
|
+
def close
|
175
|
+
@accept_worker_thread&.kill
|
176
|
+
orig_close
|
177
|
+
end
|
178
|
+
|
113
179
|
def accept_loop(ignore_errors = true)
|
114
180
|
loop do
|
115
181
|
yield accept
|
@@ -5,6 +5,12 @@ require 'socket'
|
|
5
5
|
require_relative './io'
|
6
6
|
require_relative '../core/thread_pool'
|
7
7
|
|
8
|
+
class BasicSocket
|
9
|
+
def __polyphony_read_method__
|
10
|
+
:backend_recv
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
# Socket overrides (eventually rewritten in C)
|
9
15
|
class ::Socket
|
10
16
|
def accept
|
@@ -200,7 +206,7 @@ class ::TCPSocket
|
|
200
206
|
# Polyphony.backend_send(self, mesg, 0)
|
201
207
|
# end
|
202
208
|
|
203
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
209
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
204
210
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
205
211
|
raise EOFError if !result && raise_on_eof
|
206
212
|
result
|
@@ -293,7 +299,7 @@ class ::UNIXSocket
|
|
293
299
|
Polyphony.backend_send(self, mesg, 0)
|
294
300
|
end
|
295
301
|
|
296
|
-
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof)
|
302
|
+
def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
|
297
303
|
result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
|
298
304
|
raise EOFError if !result && raise_on_eof
|
299
305
|
result
|
data/lib/polyphony/net.rb
CHANGED
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -15,6 +15,8 @@ require 'minitest/reporters'
|
|
15
15
|
|
16
16
|
::Exception.__disable_sanitized_backtrace__ = true
|
17
17
|
|
18
|
+
IS_LINUX = RUBY_PLATFORM =~ /linux/
|
19
|
+
|
18
20
|
# Minitest::Reporters.use! [
|
19
21
|
# Minitest::Reporters::SpecReporter.new
|
20
22
|
# ]
|
@@ -44,12 +46,7 @@ end
|
|
44
46
|
class MiniTest::Test
|
45
47
|
def setup
|
46
48
|
# trace "* setup #{self.name}"
|
47
|
-
if Fiber.current.children.size > 0
|
48
|
-
puts "Children left: #{Fiber.current.children.inspect}"
|
49
|
-
exit!
|
50
|
-
end
|
51
49
|
Fiber.current.setup_main_fiber
|
52
|
-
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
53
50
|
Thread.current.backend.finalize
|
54
51
|
Thread.current.backend = Polyphony::Backend.new
|
55
52
|
sleep 0.001
|
@@ -58,6 +55,10 @@ class MiniTest::Test
|
|
58
55
|
def teardown
|
59
56
|
# trace "* teardown #{self.name}"
|
60
57
|
Fiber.current.shutdown_all_children
|
58
|
+
if Fiber.current.children.size > 0
|
59
|
+
puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
|
60
|
+
exit!
|
61
|
+
end
|
61
62
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
62
63
|
rescue => e
|
63
64
|
puts e
|
data/test/stress.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
count = ARGV[0] ? ARGV[0].to_i : 100
|
4
|
+
test_name = ARGV[1]
|
4
5
|
|
5
|
-
|
6
|
+
$test_cmd = +'ruby test/run.rb'
|
7
|
+
if test_name
|
8
|
+
$test_cmd << " --name #{test_name}"
|
9
|
+
end
|
6
10
|
|
7
11
|
def run_test(count)
|
8
12
|
puts "#{count}: running tests..."
|
9
13
|
# sleep 1
|
10
|
-
system(
|
14
|
+
system($test_cmd)
|
11
15
|
puts
|
12
16
|
|
13
17
|
return if $?.exitstatus == 0
|
data/test/test_backend.rb
CHANGED
@@ -26,7 +26,7 @@ class BackendTest < MiniTest::Test
|
|
26
26
|
@backend.sleep 0.01
|
27
27
|
count += 1
|
28
28
|
}.await
|
29
|
-
assert_in_range 0.02..0.
|
29
|
+
assert_in_range 0.02..0.06, Time.now - t0 if IS_LINUX
|
30
30
|
assert_equal 3, count
|
31
31
|
end
|
32
32
|
|
@@ -243,6 +243,8 @@ class BackendTest < MiniTest::Test
|
|
243
243
|
end
|
244
244
|
|
245
245
|
def test_timer_loop
|
246
|
+
skip unless IS_LINUX
|
247
|
+
|
246
248
|
i = 0
|
247
249
|
f = spin do
|
248
250
|
@backend.timer_loop(0.01) { i += 1 }
|
@@ -257,6 +259,8 @@ class BackendTest < MiniTest::Test
|
|
257
259
|
end
|
258
260
|
|
259
261
|
def test_timeout
|
262
|
+
skip unless IS_LINUX
|
263
|
+
|
260
264
|
buffer = []
|
261
265
|
assert_raises(Polyphony::TimeoutException) do
|
262
266
|
@backend.timeout(0.01, Polyphony::TimeoutException) do
|
@@ -288,6 +292,8 @@ class BackendTest < MiniTest::Test
|
|
288
292
|
end
|
289
293
|
|
290
294
|
def test_nested_timeout
|
295
|
+
skip unless IS_LINUX
|
296
|
+
|
291
297
|
buffer = []
|
292
298
|
assert_raises(MyTimeoutException) do
|
293
299
|
@backend.timeout(0.01, MyTimeoutException) do
|
@@ -347,8 +353,8 @@ class BackendTest < MiniTest::Test
|
|
347
353
|
|
348
354
|
|
349
355
|
def test_splice_chunks
|
350
|
-
body = 'abcd' *
|
351
|
-
chunk_size =
|
356
|
+
body = 'abcd' * 4
|
357
|
+
chunk_size = 12
|
352
358
|
|
353
359
|
buf = +''
|
354
360
|
r, w = IO.pipe
|
@@ -373,7 +379,7 @@ class BackendTest < MiniTest::Test
|
|
373
379
|
w.close
|
374
380
|
reader.await
|
375
381
|
|
376
|
-
expected = "Content-Type: foo\r\n\r\n#{
|
382
|
+
expected = "Content-Type: foo\r\n\r\n#{12.to_s(16)}\r\n#{body[0..11]}\r\n#{4.to_s(16)}\r\n#{body[12..15]}\r\n0\r\n\r\n"
|
377
383
|
assert_equal expected, buf
|
378
384
|
ensure
|
379
385
|
o.close
|
@@ -394,6 +400,9 @@ class BackendTest < MiniTest::Test
|
|
394
400
|
assert_equal count, GC.count
|
395
401
|
sleep 0.05
|
396
402
|
assert_equal count, GC.count
|
403
|
+
|
404
|
+
return unless IS_LINUX
|
405
|
+
|
397
406
|
# The idle tasks are ran at most once per fiber switch, before the backend
|
398
407
|
# is polled. Therefore, the second sleep will not have triggered a GC, since
|
399
408
|
# only 0.05s have passed since the gc period was set.
|
data/test/test_fiber.rb
CHANGED
@@ -46,7 +46,7 @@ class FiberTest < MiniTest::Test
|
|
46
46
|
def test_await_dead_children
|
47
47
|
f1 = spin { :foo }
|
48
48
|
f2 = spin { :bar }
|
49
|
-
|
49
|
+
4.times { snooze }
|
50
50
|
|
51
51
|
assert_equal [:foo, :bar], Fiber.await(f1, f2)
|
52
52
|
end
|
@@ -67,10 +67,12 @@ class FiberTest < MiniTest::Test
|
|
67
67
|
}
|
68
68
|
Fiber.await(f2, f3)
|
69
69
|
assert_equal [:foo, :bar, :baz], buffer
|
70
|
-
assert_equal
|
70
|
+
assert_equal [f1], Fiber.current.children
|
71
|
+
Fiber.current.reap_dead_children
|
72
|
+
assert_equal [], Fiber.current.children
|
71
73
|
end
|
72
74
|
|
73
|
-
def test_await_from_multiple_fibers_with_interruption
|
75
|
+
def test_await_from_multiple_fibers_with_interruption
|
74
76
|
buffer = []
|
75
77
|
f1 = spin {
|
76
78
|
sleep 0.02
|
@@ -91,6 +93,8 @@ class FiberTest < MiniTest::Test
|
|
91
93
|
f1.stop
|
92
94
|
|
93
95
|
snooze
|
96
|
+
assert_equal [f1, f2, f3], Fiber.current.children
|
97
|
+
Fiber.current.reap_dead_children
|
94
98
|
assert_equal [], Fiber.current.children
|
95
99
|
end
|
96
100
|
|
@@ -563,10 +567,10 @@ class FiberTest < MiniTest::Test
|
|
563
567
|
end
|
564
568
|
|
565
569
|
snooze
|
566
|
-
child.monitor
|
570
|
+
child.monitor(Fiber.current)
|
567
571
|
spin { child << :foo }
|
568
572
|
|
569
|
-
msg =
|
573
|
+
msg = Fiber.current.monitor_mailbox.shift
|
570
574
|
assert_equal [child, :foo], msg
|
571
575
|
end
|
572
576
|
|
@@ -578,14 +582,14 @@ class FiberTest < MiniTest::Test
|
|
578
582
|
end
|
579
583
|
|
580
584
|
snooze
|
581
|
-
child.monitor
|
585
|
+
child.monitor(Fiber.current)
|
582
586
|
spin { child << :foo }
|
583
587
|
snooze
|
584
588
|
|
585
|
-
child.unmonitor
|
589
|
+
child.unmonitor(Fiber.current)
|
586
590
|
|
587
|
-
Fiber.current << :bar
|
588
|
-
msg =
|
591
|
+
Fiber.current.monitor_mailbox << :bar
|
592
|
+
msg = Fiber.current.monitor_mailbox.shift
|
589
593
|
assert_equal :bar, msg
|
590
594
|
end
|
591
595
|
|
@@ -598,6 +602,7 @@ class FiberTest < MiniTest::Test
|
|
598
602
|
|
599
603
|
f.stop
|
600
604
|
snooze
|
605
|
+
Fiber.current.reap_dead_children
|
601
606
|
assert_equal [], Fiber.current.children
|
602
607
|
end
|
603
608
|
|
@@ -786,7 +791,7 @@ class FiberTest < MiniTest::Test
|
|
786
791
|
], buf
|
787
792
|
end
|
788
793
|
|
789
|
-
def
|
794
|
+
def test_attach_to
|
790
795
|
buf = []
|
791
796
|
child = nil
|
792
797
|
parent = spin(:parent) do
|
@@ -804,7 +809,7 @@ class FiberTest < MiniTest::Test
|
|
804
809
|
|
805
810
|
snooze
|
806
811
|
assert_equal parent, child.parent
|
807
|
-
child.
|
812
|
+
child.attach_to(new_parent)
|
808
813
|
assert_equal new_parent, child.parent
|
809
814
|
parent.await
|
810
815
|
|
@@ -1218,4 +1223,23 @@ class GracefulTerminationTest < MiniTest::Test
|
|
1218
1223
|
|
1219
1224
|
assert_equal [1, 2], buffer
|
1220
1225
|
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
class DebugTest < MiniTest::Test
|
1229
|
+
def test_parking
|
1230
|
+
buf = []
|
1231
|
+
f = spin do
|
1232
|
+
3.times { |i| snooze; buf << i }
|
1233
|
+
end
|
1234
|
+
assert_nil f.__parked__?
|
1235
|
+
f.__park__
|
1236
|
+
assert_equal true, f.__parked__?
|
1237
|
+
10.times { snooze }
|
1238
|
+
assert_equal [], buf
|
1239
|
+
|
1240
|
+
f.__unpark__
|
1241
|
+
assert_nil f.__parked__?
|
1242
|
+
10.times { snooze }
|
1243
|
+
assert_equal [0, 1, 2], buf
|
1244
|
+
end
|
1221
1245
|
end
|
data/test/test_global_api.rb
CHANGED
@@ -137,7 +137,7 @@ class MoveOnAfterTest < MiniTest::Test
|
|
137
137
|
t1 = Time.now
|
138
138
|
|
139
139
|
assert_nil v
|
140
|
-
assert_in_range 0.014..0.02, t1 - t0
|
140
|
+
assert_in_range 0.014..0.02, t1 - t0 if IS_LINUX
|
141
141
|
end
|
142
142
|
|
143
143
|
def test_move_on_after_without_block
|
@@ -152,6 +152,8 @@ class MoveOnAfterTest < MiniTest::Test
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def test_nested_move_on_after
|
155
|
+
skip unless IS_LINUX
|
156
|
+
|
155
157
|
t0 = Time.now
|
156
158
|
o = move_on_after(0.01, with_value: 1) do
|
157
159
|
move_on_after(0.02, with_value: 2) do
|
@@ -210,7 +212,7 @@ class CancelAfterTest < MiniTest::Test
|
|
210
212
|
sleep 0.007
|
211
213
|
end
|
212
214
|
t1 = Time.now
|
213
|
-
assert_in_range 0.014..0.024, t1 - t0
|
215
|
+
assert_in_range 0.014..0.024, t1 - t0 if IS_LINUX
|
214
216
|
end
|
215
217
|
|
216
218
|
class CustomException < Exception
|
@@ -373,6 +375,7 @@ class SpinScopeTest < MiniTest::Test
|
|
373
375
|
buffer << e.message
|
374
376
|
end
|
375
377
|
10.times { snooze }
|
378
|
+
Fiber.current.reap_dead_children
|
376
379
|
assert_equal 0, Fiber.current.children.size
|
377
380
|
assert_equal ['foobar'], buffer
|
378
381
|
end
|
@@ -399,7 +402,7 @@ class ThrottledLoopTest < MiniTest::Test
|
|
399
402
|
end
|
400
403
|
f.await
|
401
404
|
t1 = Time.now
|
402
|
-
assert_in_range 0.075..0.15, t1 - t0
|
405
|
+
assert_in_range 0.075..0.15, t1 - t0 if IS_LINUX
|
403
406
|
assert_equal [1, 2, 3, 4, 5], buffer
|
404
407
|
end
|
405
408
|
end
|
@@ -415,6 +418,8 @@ class GlobalAPIEtcTest < MiniTest::Test
|
|
415
418
|
end
|
416
419
|
|
417
420
|
def test_every
|
421
|
+
skip unless IS_LINUX
|
422
|
+
|
418
423
|
buffer = []
|
419
424
|
t0 = Time.now
|
420
425
|
f = spin do
|
@@ -429,7 +434,7 @@ class GlobalAPIEtcTest < MiniTest::Test
|
|
429
434
|
t0 = Time.now
|
430
435
|
sleep 0.1
|
431
436
|
elapsed = Time.now - t0
|
432
|
-
assert (0.05..0.15).include? elapsed
|
437
|
+
assert (0.05..0.15).include? elapsed if IS_LINUX
|
433
438
|
|
434
439
|
f = spin { sleep }
|
435
440
|
snooze
|