summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
authorKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2023-08-18 23:19:21 +1000
committerusa <usa@garbagecollect.jp>2023-09-05 20:19:55 +0900
commitc08fdc68383ee368c18e15e298502e6ee0089e18 (patch)
tree65739d00f132bcb6ad9ef118a29fedce8941aeb7 /test/ruby
parenta8670865c0c15f03b56cb09cbcc0ffc91c12807f (diff)
Allow waitpid(-1) to be woken if a waitpid(pid) call is pending
If two threads are running, with one calling waitpid(-1), and another calling waitpid($some_pid), and then $some_other_pid exits, we would expect the waitpid(-1) call to retrieve that exit status; however, it cannot actually do so until $some_pid _also_ exits. This patch fixes the issue by unconditionally checking for pending process group waits on SIGCHLD, and then allowing pending pid-only waits to "steal" the notification. [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 91d554ea32..a6de1c8e6d 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -2637,4 +2637,59 @@ EOS
end
end;
end if Process.respond_to?(:_fork)
+
+ 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