mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Allow waitpid(-1, Process::WNOHANG) to be woken if a waitpid(pid) is
...pending If two threads are running, with one calling waitpid(-1, Process::WNOHANG), and another calling waitpid($some_pid), and then $some_other_pid exits, we would expect the waitpid(-1, Process::WNOHANG) call to retrieve that exit status. However, it cannot actually do so until $some_pid _also_ exits. This patch fixes the issue by calling do_waitpid unconditionally in waitpid_wait; this will ensure that a waitpid -1 actually reaps something (after first checking that no PID-directed call wants the process). [Bug #20490]
This commit is contained in:
parent
50399eebd9
commit
65fed1c3e4
2 changed files with 43 additions and 11 deletions
24
process.c
24
process.c
|
@ -1270,6 +1270,7 @@ waitpid_cleanup(VALUE x)
|
|||
return Qfalse;
|
||||
}
|
||||
|
||||
#if RUBY_SIGCHLD
|
||||
static void
|
||||
waitpid_wait(struct waitpid_state *w)
|
||||
{
|
||||
|
@ -1283,10 +1284,17 @@ waitpid_wait(struct waitpid_state *w)
|
|||
*/
|
||||
rb_native_mutex_lock(&vm->waitpid_lock);
|
||||
|
||||
if (w->pid > 0 || ccan_list_empty(&vm->waiting_pids)) {
|
||||
w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
|
||||
if (w->options & WNOHANG && w->pid <= 0) {
|
||||
/* In the case of WNOHANG wait for a group, make sure there isn't a zombie child
|
||||
* whose PID we are directly waiting for in another call to waitpid. If there is,
|
||||
* we will reap it via a call to waitpid($pid) with this call to waitpid_each. */
|
||||
waitpid_each(vm, &vm->waiting_pids);
|
||||
/* _now_ it's safe to call do_waitpid, without risk of stealing the wait from
|
||||
* another directed call. */
|
||||
}
|
||||
|
||||
w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
|
||||
|
||||
if (w->ret) {
|
||||
if (w->ret == -1) w->errnum = errno;
|
||||
}
|
||||
|
@ -1308,6 +1316,7 @@ waitpid_wait(struct waitpid_state *w)
|
|||
rb_ensure(waitpid_sleep, (VALUE)w, waitpid_cleanup, (VALUE)w);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *
|
||||
waitpid_blocking_no_SIGCHLD(void *x)
|
||||
|
@ -1350,12 +1359,11 @@ rb_process_status_wait(rb_pid_t pid, int flags)
|
|||
waitpid_state_init(&waitpid_state, pid, flags);
|
||||
waitpid_state.ec = GET_EC();
|
||||
|
||||
if (WAITPID_USE_SIGCHLD) {
|
||||
waitpid_wait(&waitpid_state);
|
||||
}
|
||||
else {
|
||||
waitpid_no_SIGCHLD(&waitpid_state);
|
||||
}
|
||||
#if WAITPID_USE_SIGCHLD
|
||||
waitpid_wait(&waitpid_state);
|
||||
#else
|
||||
waitpid_no_SIGCHLD(&waitpid_state);
|
||||
#endif
|
||||
|
||||
if (waitpid_state.ret == 0) return Qnil;
|
||||
|
||||
|
|
|
@ -2659,7 +2659,7 @@ EOS
|
|||
end;
|
||||
end if Process.respond_to?(:_fork)
|
||||
|
||||
def test_concurrent_group_and_pid_wait
|
||||
def _test_concurrent_group_and_pid_wait(nohang)
|
||||
# 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
|
||||
|
@ -2681,10 +2681,26 @@ EOS
|
|||
end
|
||||
# Wait for us to be blocking in a call to waitpid2
|
||||
Thread.pass until t1.stop?
|
||||
assert_nil Process.waitpid(-1, Process::WNOHANG)
|
||||
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)
|
||||
# The short pid has exited, so waitpid(-1) should pick that up.
|
||||
exited_pid =
|
||||
unless nohang
|
||||
Process.waitpid(-1)
|
||||
else
|
||||
EnvUtil.timeout(5) do
|
||||
loop do
|
||||
pid = Process.waitpid(-1, Process::WNOHANG)
|
||||
|
||||
break pid if pid
|
||||
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal short_pid, exited_pid
|
||||
|
||||
# Terminate t1 for the next phase of the test.
|
||||
t1.kill
|
||||
|
@ -2714,6 +2730,14 @@ EOS
|
|||
[long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
|
||||
end if defined?(fork)
|
||||
|
||||
def test_concurrent_group_and_pid_wait
|
||||
_test_concurrent_group_and_pid_wait(false)
|
||||
end if defined?(fork)
|
||||
|
||||
def test_concurrent_group_and_pid_wait_nohang
|
||||
_test_concurrent_group_and_pid_wait(true)
|
||||
end if defined?(fork)
|
||||
|
||||
def test_handle_interrupt_with_fork
|
||||
Thread.handle_interrupt(RuntimeError => :never) do
|
||||
Thread.current.raise(RuntimeError, "Queued error")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue