polyphony 1.4 → 1.6

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +22 -0
  4. data/TODO.md +5 -14
  5. data/examples/pipes/http_server.rb +42 -12
  6. data/examples/pipes/http_server2.rb +45 -0
  7. data/ext/polyphony/backend_common.h +5 -0
  8. data/ext/polyphony/backend_io_uring.c +174 -121
  9. data/ext/polyphony/backend_io_uring_context.c +24 -18
  10. data/ext/polyphony/backend_io_uring_context.h +4 -2
  11. data/ext/polyphony/backend_libev.c +46 -22
  12. data/ext/polyphony/event.c +21 -0
  13. data/ext/polyphony/extconf.rb +25 -19
  14. data/ext/polyphony/fiber.c +0 -2
  15. data/ext/polyphony/pipe.c +1 -1
  16. data/ext/polyphony/polyphony.c +2 -20
  17. data/ext/polyphony/polyphony.h +5 -5
  18. data/ext/polyphony/ring_buffer.c +1 -0
  19. data/ext/polyphony/runqueue_ring_buffer.c +1 -0
  20. data/ext/polyphony/thread.c +63 -0
  21. data/ext/polyphony/win_uio.h +18 -0
  22. data/lib/polyphony/adapters/open3.rb +190 -0
  23. data/lib/polyphony/core/sync.rb +83 -13
  24. data/lib/polyphony/core/timer.rb +7 -25
  25. data/lib/polyphony/extensions/exception.rb +15 -0
  26. data/lib/polyphony/extensions/fiber.rb +14 -13
  27. data/lib/polyphony/extensions/io.rb +56 -14
  28. data/lib/polyphony/extensions/kernel.rb +1 -1
  29. data/lib/polyphony/extensions/object.rb +1 -13
  30. data/lib/polyphony/extensions/process.rb +76 -1
  31. data/lib/polyphony/extensions/socket.rb +0 -14
  32. data/lib/polyphony/extensions/thread.rb +19 -27
  33. data/lib/polyphony/extensions/timeout.rb +5 -1
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +11 -5
  36. data/test/helper.rb +46 -4
  37. data/test/open3/envutil.rb +380 -0
  38. data/test/open3/find_executable.rb +24 -0
  39. data/test/stress.rb +11 -7
  40. data/test/test_backend.rb +11 -4
  41. data/test/test_event.rb +10 -3
  42. data/test/test_ext.rb +16 -1
  43. data/test/test_fiber.rb +16 -4
  44. data/test/test_global_api.rb +17 -16
  45. data/test/test_io.rb +39 -0
  46. data/test/test_kernel.rb +2 -2
  47. data/test/test_monitor.rb +356 -0
  48. data/test/test_open3.rb +338 -0
  49. data/test/test_signal.rb +5 -1
  50. data/test/test_socket.rb +6 -98
  51. data/test/test_sync.rb +46 -0
  52. data/test/test_thread.rb +10 -1
  53. data/test/test_thread_pool.rb +5 -0
  54. data/test/test_throttler.rb +1 -1
  55. data/test/test_timer.rb +8 -2
  56. data/test/test_trace.rb +2 -0
  57. data/vendor/liburing/.github/workflows/build.yml +8 -0
  58. data/vendor/liburing/.gitignore +1 -0
  59. data/vendor/liburing/CHANGELOG +8 -0
  60. data/vendor/liburing/configure +17 -25
  61. data/vendor/liburing/debian/liburing-dev.manpages +2 -0
  62. data/vendor/liburing/debian/rules +2 -1
  63. data/vendor/liburing/examples/Makefile +2 -1
  64. data/vendor/liburing/examples/io_uring-udp.c +11 -3
  65. data/vendor/liburing/examples/rsrc-update-bench.c +100 -0
  66. data/vendor/liburing/liburing.spec +1 -1
  67. data/vendor/liburing/make-debs.sh +4 -2
  68. data/vendor/liburing/src/Makefile +5 -5
  69. data/vendor/liburing/src/arch/aarch64/lib.h +1 -1
  70. data/vendor/liburing/src/include/liburing/io_uring.h +41 -16
  71. data/vendor/liburing/src/include/liburing.h +86 -11
  72. data/vendor/liburing/src/int_flags.h +1 -0
  73. data/vendor/liburing/src/liburing-ffi.map +12 -0
  74. data/vendor/liburing/src/liburing.map +8 -0
  75. data/vendor/liburing/src/register.c +7 -2
  76. data/vendor/liburing/src/setup.c +373 -81
  77. data/vendor/liburing/test/232c93d07b74.c +3 -3
  78. data/vendor/liburing/test/Makefile +10 -3
  79. data/vendor/liburing/test/accept.c +2 -1
  80. data/vendor/liburing/test/buf-ring.c +35 -75
  81. data/vendor/liburing/test/connect-rep.c +204 -0
  82. data/vendor/liburing/test/coredump.c +59 -0
  83. data/vendor/liburing/test/fallocate.c +9 -0
  84. data/vendor/liburing/test/fd-pass.c +34 -3
  85. data/vendor/liburing/test/file-verify.c +27 -6
  86. data/vendor/liburing/test/helpers.c +3 -1
  87. data/vendor/liburing/test/io_uring_register.c +25 -28
  88. data/vendor/liburing/test/io_uring_setup.c +1 -1
  89. data/vendor/liburing/test/poll-cancel-all.c +29 -5
  90. data/vendor/liburing/test/poll-race-mshot.c +6 -22
  91. data/vendor/liburing/test/read-write.c +53 -0
  92. data/vendor/liburing/test/recv-msgall.c +21 -23
  93. data/vendor/liburing/test/reg-fd-only.c +55 -0
  94. data/vendor/liburing/test/reg-hint.c +56 -0
  95. data/vendor/liburing/test/regbuf-merge.c +91 -0
  96. data/vendor/liburing/test/ringbuf-read.c +2 -10
  97. data/vendor/liburing/test/send_recvmsg.c +5 -16
  98. data/vendor/liburing/test/shutdown.c +2 -1
  99. data/vendor/liburing/test/socket-io-cmd.c +215 -0
  100. data/vendor/liburing/test/socket-rw-eagain.c +2 -1
  101. data/vendor/liburing/test/socket-rw-offset.c +2 -1
  102. data/vendor/liburing/test/socket-rw.c +2 -1
  103. data/vendor/liburing/test/timeout.c +276 -0
  104. data/vendor/liburing/test/xattr.c +38 -25
  105. metadata +20 -7
  106. data/vendor/liburing/test/timeout-overflow.c +0 -204
@@ -2,6 +2,81 @@
2
2
 
3
3
  # Overrides for Process module
4
4
  module ::Process
5
+ module StatusExtensions
6
+ def coredump?
7
+ @status ? nil : super
8
+ end
9
+
10
+ def exited?
11
+ @status ? WIFEXITED(@status[1]) : super
12
+ end
13
+
14
+ def exitstatus
15
+ @status ? WEXITSTATUS(@status[1]) : super
16
+ end
17
+
18
+ def inspect
19
+ @status ? "#<Process::Status: pid #{@status[0]} exit #{@status[1]}>" : super
20
+ end
21
+
22
+ def pid
23
+ @status ? @status[0] : super
24
+ end
25
+
26
+ def signaled?
27
+ @status ? WIFSIGNALED(@status[1]) : super
28
+ end
29
+
30
+ def stopped?
31
+ @status ? WIFSTOPPED(@status[1]) : super
32
+ end
33
+
34
+ def stopsig
35
+ @status ? WIFSTOPPED(@status[1]) && WEXITSTATUS(@status[1]) : super
36
+ end
37
+
38
+ def success?
39
+ @status ? @status[1] == 0 : super
40
+ end
41
+
42
+ def termsig
43
+ @status ? WIFSIGNALED(@status[1]) && WTERMSIG(@status[1]) : super
44
+ end
45
+
46
+ private
47
+
48
+ # The following helper methods are translated from the C source:
49
+ # https://github.com/ruby/ruby/blob/v3_2_0/process.c
50
+
51
+ def WIFEXITED(w)
52
+ (w & 0xff) == 0
53
+ end
54
+
55
+ def WEXITSTATUS(w)
56
+ (w >> 8) & 0xff
57
+ end
58
+
59
+ def WIFSIGNALED(w)
60
+ (w & 0x7f) > 0 && ((w & 0x7f) < 0x7f)
61
+ end
62
+
63
+ def WIFSTOPPED(w)
64
+ (w & 0xff) == 0x7f
65
+ end
66
+
67
+ def WTERMSIG(w)
68
+ w & 0x7f
69
+ end
70
+ end
71
+
72
+ class Status
73
+ prepend StatusExtensions
74
+
75
+ def self.from_status_array(arr)
76
+ allocate.tap { |s| s.instance_variable_set(:@status, arr) }
77
+ end
78
+ end
79
+
5
80
  class << self
6
81
  # @!visibility private
7
82
  alias_method :orig_detach, :detach
@@ -11,7 +86,7 @@ module ::Process
11
86
  # @param pid [Integer] child pid
12
87
  # @return [Fiber] new fiber waiting on pid
13
88
  def detach(pid)
14
- fiber = spin { Polyphony.backend_waitpid(pid) }
89
+ fiber = spin { ::Process::Status.from_status_array(Polyphony.backend_waitpid(pid)) }
15
90
  fiber.define_singleton_method(:pid) { pid }
16
91
  fiber
17
92
  end
@@ -464,20 +464,6 @@ class ::TCPServer < ::TCPSocket
464
464
  Polyphony.backend_accept(@io, TCPSocket)
465
465
  end
466
466
 
467
- if Polyphony.instance_methods(false).include?(:backend_multishot_accept)
468
- # Starts a multishot accept operation (only available with io_uring
469
- # backend). Example usage:
470
- #
471
- # server.multishot_accept do
472
- # server.accept_loop { |c| handle_connection(c) }
473
- # end
474
- #
475
- # @return [any] return value of code block
476
- def multishot_accept(&block)
477
- Polyphony.backend_multishot_accept(@io, &block)
478
- end
479
- end
480
-
481
467
  # Accepts incoming connections in an infinite loop.
482
468
  #
483
469
  # @yield [TCPSocket] accepted socket
@@ -14,7 +14,6 @@ class ::Thread
14
14
  # @param args [Array] arguments to pass to thread block
15
15
  def initialize(*args, &block)
16
16
  @join_wait_queue = []
17
- @finalization_mutex = Mutex.new
18
17
  @args = args
19
18
  @block = block
20
19
  orig_initialize { execute }
@@ -41,16 +40,7 @@ class ::Thread
41
40
  # @param timeout [Number] timeout interval
42
41
  # @return [any] thread's return value
43
42
  def join(timeout = nil)
44
- watcher = Fiber.current.auto_watcher
45
-
46
- @finalization_mutex.synchronize do
47
- if @terminated
48
- @result.is_a?(Exception) ? (raise @result) : (return @result)
49
- else
50
- @join_wait_queue << watcher
51
- end
52
- end
53
- timeout ? move_on_after(timeout) { watcher.await } : watcher.await
43
+ timeout ? move_on_after(timeout) { await_done } : await_done
54
44
  end
55
45
  alias_method :await, :join
56
46
 
@@ -62,13 +52,14 @@ class ::Thread
62
52
  #
63
53
  # @param error [Exception, Class, nil] exception spec
64
54
  def raise(error = nil)
65
- Thread.pass until @main_fiber
66
- error = RuntimeError.new if error.nil?
67
- error = RuntimeError.new(error) if error.is_a?(String)
68
- error = error.new if error.is_a?(Class)
69
-
70
55
  sleep 0.0001 until @ready
71
- main_fiber&.raise(error)
56
+
57
+ error = Exception.instantiate(error)
58
+ if Thread.current == self
59
+ Kernel.raise(error)
60
+ else
61
+ @main_fiber&.raise(error)
62
+ end
72
63
  end
73
64
 
74
65
  # @!visibility private
@@ -77,11 +68,7 @@ class ::Thread
77
68
  # Terminates the thread.
78
69
  #
79
70
  # @return [Thread] self
80
- def kill
81
- return self if @terminated
82
-
83
- raise Polyphony::Terminate
84
- end
71
+ alias_method :kill, :kill_safe
85
72
 
86
73
  # @!visibility private
87
74
  alias_method :orig_inspect, :inspect
@@ -128,6 +115,11 @@ class ::Thread
128
115
  backend.idle_proc = block
129
116
  end
130
117
 
118
+ def value
119
+ join
120
+ @result.is_a?(Exception) ? raise(@result) : @result
121
+ end
122
+
131
123
  private
132
124
 
133
125
  # Runs the thread's block, handling any uncaught exceptions.
@@ -159,13 +151,13 @@ class ::Thread
159
151
  #
160
152
  # @param result [any] thread's return value
161
153
  def finalize(result)
154
+ # We need to make sure the fiber is not on the runqueue. This, in order to
155
+ # prevent a race condition between #finalize and #kill.
156
+ fiber_unschedule(Fiber.current)
162
157
  Fiber.current.shutdown_all_children if !Fiber.current.children.empty?
163
158
 
164
- @finalization_mutex.synchronize do
165
- @terminated = true
166
- @result = result
167
- signal_waiters(result)
168
- end
159
+ @result = result
160
+ mark_as_done(result)
169
161
  @backend&.finalize
170
162
  end
171
163
 
@@ -14,6 +14,10 @@ module ::Timeout
14
14
  # @param message [String] exception message
15
15
  # @return [any] block's return value
16
16
  def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
17
- cancel_after(sec, with_exception: [klass, message], &block)
17
+ if sec.nil? || sec == 0
18
+ block.call
19
+ else
20
+ cancel_after(sec, with_exception: [klass, message], &block)
21
+ end
18
22
  end
19
23
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Polyphony
4
4
  # @!visibility private
5
- VERSION = '1.4'
5
+ VERSION = '1.6'
6
6
  end
data/lib/polyphony.rb CHANGED
@@ -14,6 +14,7 @@ require_relative './polyphony/core/sync'
14
14
  require_relative './polyphony/core/timer'
15
15
  require_relative './polyphony/net'
16
16
  require_relative './polyphony/adapters/process'
17
+ require_relative './polyphony/adapters/open3'
17
18
 
18
19
  # Polyphony API
19
20
  module Polyphony
@@ -119,10 +120,10 @@ module Polyphony
119
120
  # processes,) we use a separate mechanism to terminate fibers in forked
120
121
  # processes (see Polyphony.fork).
121
122
  at_exit do
122
- next unless @original_pid == ::Process.pid
123
-
124
- terminate_threads
125
- Fiber.current.shutdown_all_children
123
+ if @original_pid == ::Process.pid
124
+ terminate_threads
125
+ Fiber.current.shutdown_all_children
126
+ end
126
127
  end
127
128
  end
128
129
  end
@@ -131,12 +132,17 @@ module Polyphony
131
132
  verbose = $VERBOSE
132
133
  $VERBOSE = nil
133
134
  Object.const_set(:Queue, Polyphony::Queue)
135
+ Thread.const_set(:Queue, Polyphony::Queue)
136
+
134
137
  Object.const_set(:Mutex, Polyphony::Mutex)
138
+ Thread.const_set(:Mutex, Polyphony::Mutex)
135
139
 
136
140
  require 'monitor'
137
- Object.const_set(:Monitor, Polyphony::Mutex)
141
+ Object.const_set(:Monitor, Polyphony::Monitor)
138
142
 
139
143
  Object.const_set(:ConditionVariable, Polyphony::ConditionVariable)
144
+ Thread.const_set(:ConditionVariable, Polyphony::ConditionVariable)
145
+
140
146
  $VERBOSE = verbose
141
147
 
142
148
  install_terminating_signal_handlers
data/test/helper.rb CHANGED
@@ -17,6 +17,14 @@ require 'minitest/autorun'
17
17
  IS_LINUX = RUBY_PLATFORM =~ /linux/
18
18
 
19
19
  module ::Kernel
20
+ def debug(**h)
21
+ k, v = h.first
22
+ h.delete(k)
23
+
24
+ rest = h.inject(+'') { |s, (k, v)| s << " #{k}: #{v.inspect}\n" }
25
+ STDOUT.orig_write("#{k}=>#{v} #{caller[0]}\n#{rest}")
26
+ end
27
+
20
28
  def trace(*args)
21
29
  STDOUT.orig_write(format_trace(args))
22
30
  end
@@ -41,20 +49,24 @@ end
41
49
  class MiniTest::Test
42
50
  def setup
43
51
  # trace "* setup #{self.name}"
44
- Fiber.current.setup_main_fiber
52
+ @__stamp = Time.now
45
53
  Thread.current.backend.finalize
46
54
  Thread.current.backend = Polyphony::Backend.new
47
- sleep 0.001
48
- @__stamp = Time.now
55
+ Fiber.current.setup_main_fiber
56
+ Fiber.current.instance_variable_set(:@auto_watcher, nil)
57
+ sleep 0.0001
49
58
  end
50
59
 
51
60
  def teardown
52
- # trace "* teardown #{self.name} (#{Time.now - @__stamp}s)"
61
+ Polyphony::ThreadPool.reset
62
+
63
+ # trace "* teardown #{self.name} (#{@__stamp ? (Time.now - @__stamp) : '?'}s)"
53
64
  Fiber.current.shutdown_all_children
54
65
  if Fiber.current.children.size > 0
55
66
  puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
56
67
  exit!
57
68
  end
69
+ # trace "* teardown done"
58
70
  rescue => e
59
71
  puts e
60
72
  puts e.backtrace.join("\n")
@@ -79,6 +91,36 @@ module Minitest::Assertions
79
91
  msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
80
92
  assert exp_range.include?(act), msg
81
93
  end
94
+
95
+ def assert_join_threads(threads, message = nil)
96
+ errs = []
97
+ values = []
98
+ while th = threads.shift
99
+ begin
100
+ values << th.value
101
+ rescue Exception
102
+ errs << [th, $!]
103
+ th = nil
104
+ end
105
+ end
106
+ values
107
+ ensure
108
+ if th&.alive?
109
+ th.raise(Timeout::Error.new)
110
+ th.join rescue errs << [th, $!]
111
+ end
112
+ if !errs.empty?
113
+ msg = "exceptions on #{errs.length} threads:\n" +
114
+ errs.map {|t, err|
115
+ "#{t.inspect}:\n" +
116
+ (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message)
117
+ }.join("\n---\n")
118
+ if message
119
+ msg = "#{message}\n#{msg}"
120
+ end
121
+ raise MiniTest::Assertion, msg
122
+ end
123
+ end
82
124
  end
83
125
 
84
126
  puts "Polyphony backend: #{Thread.current.backend.kind}"