polyphony 0.45.5 → 0.46.0

Sign up to get free protection for your applications and to get access to all the features.
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