From: Sam Vilain Date: Fri, 31 Jul 2009 03:32:14 +0000 (+1200) Subject: pod/perlipc.pod: add some hints on avoiding pipe deadlocks X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=c40e8e9bf43b15cbc5725b65e3085fba60a67489;p=p5sagit%2Fp5-mst-13.2.git pod/perlipc.pod: add some hints on avoiding pipe deadlocks Tracking down deadlocks when using pipes for IPC can be hard, so put even more notes about gotchas in this section of perlipc. --- diff --git a/pod/perlipc.pod b/pod/perlipc.pod index 416ded5..77f0b6e 100644 --- a/pod/perlipc.pod +++ b/pod/perlipc.pod @@ -630,6 +630,68 @@ And here's a safe pipe open for writing: # NOTREACHED } +It is very easy to dead-lock a process using this form of open(), or +indeed any use of pipe() and multiple sub-processes. The above +example is 'safe' because it is simple and calls exec(). See +L for general safety principles, but there +are extra gotchas with Safe Pipe Opens. + +In particular, if you opened the pipe using C, then you +cannot simply use close() in the parent process to close an unwanted +writer. Consider this code: + + $pid = open WRITER, "|-"; + defined $pid or die "fork failed; $!"; + if ($pid) { + if (my $sub_pid = fork()) { + close WRITER; + # do something else... + } + else { + # write to WRITER... + exit; + } + } + else { + # do something with STDIN... + exit; + } + +In the above, the true parent does not want to write to the WRITER +filehandle, so it closes it. However, because WRITER was opened using +C, it has a special behaviour: closing it will call +waitpid() (see L), which waits for the sub-process +to exit. If the child process ends up waiting for something happening +in the section marked "do something else", then you have a deadlock. + +This can also be a problem with intermediate sub-processes in more +complicated code, which will call waitpid() on all open filehandles +during global destruction; in no predictable order. + +To solve this, you must manually use pipe(), fork(), and the form of +open() which sets one file descriptor to another, as below: + + pipe(READER, WRITER); + $pid = fork(); + defined $pid or die "fork failed; $!"; + if ($pid) { + close READER; + if (my $sub_pid = fork()) { + close WRITER; + } + else { + # write to WRITER... + exit; + } + # write to WRITER... + } + else { + open STDIN, "<&READER"; + close WRITER; + # do something... + exit; + } + Since Perl 5.8.0, you can also use the list form of C for pipes : the syntax @@ -645,6 +707,30 @@ correctly implemented on alien systems. Additionally, these are not true multithreading. If you'd like to learn more about threading, see the F file mentioned below in the SEE ALSO section. +=head2 Avoiding Pipe Deadlocks + +In general, if you have more than one sub-process, you need to be very +careful that any process which does not need the writer half of any +pipe you create for inter-process communication does not have it open. + +The reason for this is that any child process which is reading from +the pipe and expecting an EOF will never receive it, and therefore +never exit. A single process closing a pipe is not enough to close it; +the last process with the pipe open must close it for it to read EOF. + +There are some features built-in to unix to help prevent this most of +the time. For instance, filehandles have a 'close on exec' flag (set +I with Perl using the C<$^F> L), so that any +filehandles which you didn't explicitly route to the STDIN, STDOUT or +STDERR of a child I will automatically be closed for you. + +So, always explicitly and immediately call close() on the writable end +of any pipe, unless that process is actually writing to it. If you +don't explicitly call close() then be warned Perl will still close() +all the filehandles during global destruction. As warned above, if +those filehandles were opened with Safe Pipe Open, they will also call +waitpid() and you might again deadlock. + =head2 Bidirectional Communication with Another Process While this works reasonably well for unidirectional communication, what