1

I am trying to understand the behaviour of programs that launch subprocesses, when run in a pipeline.

This bash program, fork.sh, prints and returns immediately:

(sleep 1) &
echo 'here'

But when connected to a pipe, the read end seems to wait for the sleep to complete.

$ time bash fork.sh | wc
       1       1       5

real    0m1.014s

I've also tried this in Ruby, with some extra calls to try to prevent the sleep from blocking:

Process.detach(fork { sleep 1 })
puts 'here'
fork {
  sleep 1
  Process.daemon
}
puts 'here'

But they behave the same.

I would like to know what causes this (in terms of Unix, file descriptors, etc), and if there's a way to re-write any of these so that the pipeline returns in under a second.


edit: the answer below helped me notice the problem with the Ruby example: the daemon() call must come first. I had thought it was somehow applied to the entire process.

2
  1. child processes inherit all the file descriptors from their parents.
  2. When executing a command (like your sleep here assuming your shell doesn't have it builtin), only the file descriptors marked with the close-on-exec flag are closed, but shells never set that flag on stdout (fd 1).
  3. a pipe reader will only get an EOF when all the file descriptors pointing to its writing end have been closed.
time bash fork.sh | wc

You should have your sleep process (as started from fork.sh) give up on its stdout, which points to the write end of the pipe wc is reading from; in fork.sh:

(sleep 1 >/dev/null) &
echo 'here'

In this case sleep .. >&- (which closes the stdout, without redirecting it elsewhere) could work too, but I don't recommend this in general, because if the process opens some file afterwards, the returned file descriptor will be 1 = stdout, which may break assumptions and trigger bugs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.