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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +4 -4
  9. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -2
  10. data/ext/liburing/liburing.h +585 -0
  11. data/ext/liburing/liburing/README.md +4 -0
  12. data/ext/liburing/liburing/barrier.h +73 -0
  13. data/ext/liburing/liburing/compat.h +15 -0
  14. data/ext/liburing/liburing/io_uring.h +343 -0
  15. data/ext/liburing/queue.c +333 -0
  16. data/ext/liburing/register.c +187 -0
  17. data/ext/liburing/setup.c +210 -0
  18. data/ext/liburing/syscall.c +54 -0
  19. data/ext/liburing/syscall.h +18 -0
  20. data/ext/polyphony/backend.h +0 -14
  21. data/ext/polyphony/backend_common.h +109 -0
  22. data/ext/polyphony/backend_io_uring.c +884 -0
  23. data/ext/polyphony/backend_io_uring_context.c +73 -0
  24. data/ext/polyphony/backend_io_uring_context.h +52 -0
  25. data/ext/polyphony/{libev_backend.c → backend_libev.c} +202 -294
  26. data/ext/polyphony/event.c +1 -1
  27. data/ext/polyphony/extconf.rb +31 -13
  28. data/ext/polyphony/fiber.c +29 -22
  29. data/ext/polyphony/libev.c +4 -0
  30. data/ext/polyphony/libev.h +8 -2
  31. data/ext/polyphony/liburing.c +8 -0
  32. data/ext/polyphony/playground.c +51 -0
  33. data/ext/polyphony/polyphony.c +5 -5
  34. data/ext/polyphony/polyphony.h +16 -12
  35. data/ext/polyphony/polyphony_ext.c +10 -4
  36. data/ext/polyphony/queue.c +1 -1
  37. data/ext/polyphony/thread.c +11 -9
  38. data/lib/polyphony/adapters/trace.rb +2 -2
  39. data/lib/polyphony/core/global_api.rb +1 -4
  40. data/lib/polyphony/extensions/debug.rb +13 -0
  41. data/lib/polyphony/extensions/fiber.rb +2 -2
  42. data/lib/polyphony/extensions/socket.rb +59 -10
  43. data/lib/polyphony/version.rb +1 -1
  44. data/test/helper.rb +36 -4
  45. data/test/io_uring_test.rb +55 -0
  46. data/test/stress.rb +5 -2
  47. data/test/test_backend.rb +4 -6
  48. data/test/test_ext.rb +1 -2
  49. data/test/test_fiber.rb +22 -16
  50. data/test/test_global_api.rb +33 -35
  51. data/test/test_throttler.rb +3 -6
  52. data/test/test_trace.rb +7 -5
  53. 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
- fiber_ev_loop_enter fiber_ev_loop_leave
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 /fiber_/
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.ref
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.break_out_of_ev_loop(Thread.main.main_fiber, e)
178
+ Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
179
179
  end
180
- Thread.current.break_out_of_ev_loop(f, nil)
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
- outbuf ||= +''
23
- loop do
24
- result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
25
- case result
26
- when nil then raise IOError
27
- when :wait_readable then Thread.current.backend.wait_io(self, false)
28
- else
29
- return result
30
- end
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.45.5'
4
+ VERSION = '0.46.0'
5
5
  end
@@ -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
- # puts "* setup #{self.name}"
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 # apparently this helps with timer accuracy
54
+ sleep 0
36
55
  end
37
56
 
38
57
  def teardown
39
- # puts "* teardown #{self.name.inspect} Fiber.current: #{Fiber.current.inspect}"
58
+ # trace "* teardown #{self.name}"
40
59
  Fiber.current.terminate_all_children
41
60
  Fiber.current.await_all_children
42
- Fiber.current.auto_watcher = nil
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
@@ -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 { |i| run_test(i + 1) }
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)",
@@ -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
- # read_loop will snooze after every read
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
- 10.times { snooze }
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
- 10.times { snooze }
117
+ sleep 0.01
120
118
 
121
119
  assert_equal 2, clients.size
122
120
 
@@ -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
- buffer = []
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
@@ -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 test_ev_loop_anti_starve_mechanism
132
- async = Polyphony::Event.new
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
- async.signal(:foo)
137
+ event.signal(:foo)
137
138
  end
138
139
 
139
- result = move_on_after(1) { async.await }
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
- suspend
473
- rescue Exception => e
474
- raised_error = e
475
- ensure
476
- assert raised_error
477
- assert_equal 'foo', raised_error.message
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 100 }
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 100 }
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.2
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 100
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.2
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