X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=pod%2Fperlipc.pod;h=642461531d9aefb8224239da71173bb2cf1bfabe;hb=7360c6b444ea6e19b6583f9530f845bee19c7921;hp=416ded56057d991123d409f436eb82d7f9f462cc;hpb=abf724c9ab24576383f61f07468412ec5ceac8cb;p=p5sagit%2Fp5-mst-13.2.git diff --git a/pod/perlipc.pod b/pod/perlipc.pod index 416ded5..6424615 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 @@ -1043,7 +1129,7 @@ differ from the system on which it's being run: my $hisiaddr = inet_aton($host) || die "unknown host"; my $hispaddr = sockaddr_in($port, $hisiaddr); socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; - connect(SOCKET, $hispaddr) || die "bind: $!"; + connect(SOCKET, $hispaddr) || die "connect: $!"; my $rtime = ' '; read(SOCKET, $rtime, 4); close(SOCKET);