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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +10 -40
  6. data/bin/pdbg +112 -0
  7. data/examples/core/await.rb +9 -1
  8. data/ext/polyphony/backend_common.c +14 -1
  9. data/ext/polyphony/backend_common.h +3 -1
  10. data/ext/polyphony/backend_io_uring.c +85 -25
  11. data/ext/polyphony/backend_io_uring_context.c +42 -0
  12. data/ext/polyphony/backend_io_uring_context.h +6 -9
  13. data/ext/polyphony/backend_libev.c +85 -39
  14. data/ext/polyphony/fiber.c +20 -0
  15. data/ext/polyphony/polyphony.c +2 -0
  16. data/ext/polyphony/polyphony.h +5 -2
  17. data/ext/polyphony/queue.c +1 -1
  18. data/ext/polyphony/runqueue.c +7 -3
  19. data/ext/polyphony/runqueue.h +4 -3
  20. data/ext/polyphony/runqueue_ring_buffer.c +25 -14
  21. data/ext/polyphony/runqueue_ring_buffer.h +2 -0
  22. data/ext/polyphony/thread.c +2 -8
  23. data/lib/polyphony.rb +6 -0
  24. data/lib/polyphony/debugger.rb +225 -0
  25. data/lib/polyphony/extensions/debug.rb +1 -1
  26. data/lib/polyphony/extensions/fiber.rb +64 -71
  27. data/lib/polyphony/extensions/io.rb +4 -2
  28. data/lib/polyphony/extensions/openssl.rb +66 -0
  29. data/lib/polyphony/extensions/socket.rb +8 -2
  30. data/lib/polyphony/net.rb +1 -0
  31. data/lib/polyphony/version.rb +1 -1
  32. data/test/helper.rb +6 -5
  33. data/test/stress.rb +6 -2
  34. data/test/test_backend.rb +13 -4
  35. data/test/test_fiber.rb +35 -11
  36. data/test/test_global_api.rb +9 -4
  37. data/test/test_io.rb +2 -0
  38. data/test/test_socket.rb +14 -11
  39. data/test/test_supervise.rb +24 -24
  40. data/test/test_thread.rb +3 -0
  41. data/test/test_thread_pool.rb +1 -1
  42. data/test/test_throttler.rb +2 -2
  43. data/test/test_timer.rb +5 -3
  44. 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
@@ -67,6 +67,7 @@ module Polyphony
67
67
  def setup_alpn(context, protocols)
68
68
  context.alpn_protocols = protocols
69
69
  context.alpn_select_cb = lambda do |peer_protocols|
70
+ p alpn_select_cb: peer_protocols
70
71
  (protocols & peer_protocols).first
71
72
  end
72
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.64'
4
+ VERSION = '0.68'
5
5
  end
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
- TEST_CMD = 'ruby test/run.rb'
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(TEST_CMD)
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.04, Time.now - t0
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' * 250
351
- chunk_size = 750
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#{750.to_s(16)}\r\n#{body[0..749]}\r\n#{250.to_s(16)}\r\n#{body[750..999]}\r\n0\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
- 2.times { snooze }
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 0, Fiber.current.children.size
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 = receive
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 = receive
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 test_attach
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.attach(new_parent)
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
@@ -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
data/test/test_io.rb CHANGED
@@ -351,6 +351,8 @@ class IOClassMethodsTest < MiniTest::Test
351
351
  end
352
352
 
353
353
  def test_popen
354
+ skip unless IS_LINUX
355
+
354
356
  counter = 0
355
357
  timer = spin { throttled_loop(200) { counter += 1 } }
356
358