ruby/ruby — pull request #513 by csfrancis (2014-01-22 19:59:55+00:00)
Title: Fix deadlock in rb_fork_internal

The problem arises when a signal is sent at the same time as a process is being forked via `IO.popen`. In this case, the `recv_child_error` function called by `rb_fork_internal` makes a call to the `read` syscall which returns -1 with errno set to EINTR. Because the child process hasn't actually exited, this effectively deadlocks MRI, as the code in `rb_fork_internal` blocks indefinitely waiting for the child process to exit so that the exit code can be returned to the caller:

```c
error_occured = recv_child_error(ep[0], &state, &exc, &err, errmsg, errmsg_buflen, chfunc_is_async_signal_safe);
if (state || error_occured) {
if (status) {
rb_protect(proc_syswait, (VALUE)pid, status);
if (state) *status = state;
}
else {
rb_syswait(pid);
if (state) rb_exc_raise(exc);
}
```

The fix is to retry the `read` in `recv_child_error` when errno is `EINTR`. This ensures that we will not incorrectly block waiting for the child process to exit when the parent receives a signal.

We were able to reproduce this problem with the following Ruby code:

```ruby
#!/usr/bin/env ruby

trap("QUIT"){}

1000.times do |i|
puts "iteration #{i}"
fork do
Process.kill("QUIT", Process.ppid)
end
IO.popen("cat", 'r+'){}
end
```