summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
authorKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2023-08-18 23:19:21 +1000
committerKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2024-01-11 13:12:17 +1100
commit76a8c963c7ad975b7bbfc1c4979bf7a2de15af27 (patch)
tree37516471237f0ab125e82bf99a59f5026da37e49 /test/ruby
parent27688b6a1df7b58b539165c7d17b359db5142bd7 (diff)
Add a test for what happens with concurent calls to waitpid
Ruby 3.1 and 3.2 have a bug in their _implementation_, for which I'm backporting a fix. However, the current development branch doesn't have the issue (because the MJIT -> RJIT change refactored how waitpid worked substantially). I do however want to commit the test which verifies that waitpid works properly on master. [Fixes #19387]
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/test_process.rb55
1 files changed, 55 insertions, 0 deletions
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 188ef75fae..edee624665 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -2783,4 +2783,59 @@ EOS
assert_operator(GC.stat(:total_freed_pages), :>, 0)
end;
end
+
+ def test_concurrent_group_and_pid_wait
+ # Use a pair of pipes that will make long_pid exit when this test exits, to avoid
+ # leaking temp processes.
+ long_rpipe, long_wpipe = IO.pipe
+ short_rpipe, short_wpipe = IO.pipe
+ # This process should run forever
+ long_pid = fork do
+ [short_rpipe, short_wpipe, long_wpipe].each(&:close)
+ long_rpipe.read
+ end
+ # This process will exit
+ short_pid = fork do
+ [long_rpipe, long_wpipe, short_wpipe].each(&:close)
+ short_rpipe.read
+ end
+ t1, t2, t3 = nil
+ EnvUtil.timeout(5) do
+ t1 = Thread.new do
+ Process.waitpid long_pid
+ end
+ # Wait for us to be blocking in a call to waitpid2
+ Thread.pass until t1.stop?
+ short_wpipe.close # Make short_pid exit
+
+ # The short pid has exited, so -1 should pick that up.
+ assert_equal short_pid, Process.waitpid(-1)
+
+ # Terminate t1 for the next phase of the test.
+ t1.kill
+ t1.join
+
+ t2 = Thread.new do
+ Process.waitpid -1
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t2.stop?
+ t3 = Thread.new do
+ Process.waitpid long_pid
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t3.stop?
+
+ # it's actually nondeterministic which of t2 or t3 will receive the wait (this
+ # nondeterminism comes from the behaviour of the underlying system calls)
+ long_wpipe.close
+ assert_equal [long_pid], [t2, t3].map(&:value).compact
+ end
+ ensure
+ [t1, t2, t3].each { _1&.kill rescue nil }
+ [t1, t2, t3].each { _1&.join rescue nil }
+ [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
+ end if defined?(fork)
end