Bloehdian has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

this time I cannot post the actual code I am using due to an NDA (whereby: the SERVER script below is exactly what I use in production, only the CLIENT stuff must not be published.

My question is more of general nature anyway. It can be illustrated with the following code examples (which actually do not show the problem).

I have a client (script) connecting to a TCP socket on which the server (script) is listening and to which the client constantly sends short text messages. The messages are generated within a child process generated with fork.

When both the client and server are running I simulate network connectivity problems by pressing CTRL-C to interrupt the server. The server stops running and, obviously from output, parent AND child process are still running (TWO related process in the process list).

With my actual production script, which contains much more code in the child section, normally the child deceases and only the parent keeps on running (only ONE process in the process list). $! says "Broken pipe".

Is there a safe method to prevent this, i.e., to keep the child running as well?

I tried, as in the examples, catch the PIPE signal by IGNOREing it and other things, but nothing turned out to be working.

Any hint is appreciated.

CLIENT:

#/usr/bin/perl use warnings; use IO::Socket::INET; use Scalar::Util; $port = '7777'; if ( defined $ARGV[0] ) { $port = $ARGV[0] } $SIG{ 'TERM' } = $SIG{ 'INT' } = sub { print( "died() or caught TERM/INT\n" ); exit(); }; $SIG { 'PIPE' } = 'IGNORE'; pipe( $reader, $writer ); $writer->autoflush(1); if ( ! defined ( $pid = fork() ) ) { die( "Cannot fork!: $!" ) } elsif ( $pid == 0 ) { # Child # $line = ''; # A line of data to be read # Connect to socket # $connected = 0; while ( 1 ) { eval { undef $socket; $socket = IO::Socket::INET->new ( PeerHost => '127.0.0.1', PeerPort => $port, Proto => 'tcp' ); }; if ( $@ ) { print "$@\n" } if ( defined $socket ) { print( "Connected!\n" ); $connected = 1; } else { print( "Could not connect!\n" ); $connected = 0; die(); } close $writer; while ( $connected ) { defined ( $line_2 = <$reader> ) or last; print( "CHILD: Received line $line_2\n" ); eval { if ( defined $socket->send( $line_2 ) ) { print ( "CHILD: \$socket->send() successful: $line_2 +\n") ; #shutdown( $socket, 1 ); $response = ""; $socket->recv($response, 1024); print "CHILD: received response: $response\n"; } else { print( "Not connected!\n" ); $connected = 0; } }; if ( $@ ) { print( "CHILD: error when trying to send(): $@ +" ) } } close $reader; } } else { # Parent # close $reader; for ( $i=1; $i<=5; $i++ ) { $line = "-- " . $i . " --"; print( "PARENT: Going to transfer line $line\n" ); eval { if ( print $writer "$line\n") { print ( "PARENT: Successfully pushed to child!\n" ); } }; if ( $@ ) { print( "$@\n" ) } sleep 1; } close $writer; print "PARENT: closed writer, waiting for child to exit\n"; 1 while wait > 0; }
SERVER:
use IO::Socket::INET; my $port = '7777'; if ( defined $ARGV[0] ) { $port = $ARGV[0] } # auto-flush on socket $| = 1; $SIG{ 'TERM' } = $SIG{ 'INT' } = sub { $socket->close(); }; $SIG{ 'PIPE' } = 'IGNORE'; my $client_socket; # creating a listening socket my $socket = new IO::Socket::INET ( LocalHost => '0.0.0.0', LocalPort => $port, Proto => 'tcp', Listen => 5, Reuse => 1 ); die "cannot create socket $!\n" unless $socket; print "server waiting for client connection on port $port\n"; # waiting for a new client connection $client_socket = $socket->accept(); while(1) { # get information about a newly connected client my $client_address = $client_socket->peerhost(); my $client_port = $client_socket->peerport(); print "connection from $client_address:$client_port\n"; # read up to 1024 characters from the connected client my $data = ""; $client_socket->recv($data, 1024); print "received data: $data\n"; # write response data to the connected client $data = "ok"; $client_socket->send($data); # notify client that response has been sent #shutdown($client_socket, 1); }

CLIENT output:

PARENT: Going to transfer line -- 1 -- PARENT: Successfully pushed to child! Connected! CHILD: Received line -- 1 -- CHILD: $socket->send() successful: -- 1 -- CHILD: received response: ok PARENT: Going to transfer line -- 2 -- PARENT: Successfully pushed to child! CHILD: Received line -- 2 -- CHILD: $socket->send() successful: -- 2 -- CHILD: received response: ok PARENT: Going to transfer line -- 3 -- PARENT: Successfully pushed to child! CHILD: Received line -- 3 -- CHILD: $socket->send() successful: -- 3 -- CHILD: received response: PARENT: Going to transfer line -- 4 -- PARENT: Successfully pushed to child! CHILD: Received line -- 4 -- CHILD: error when trying to send(): send: Cannot determine peer addres +s at test_4.pl line 70. PARENT: Going to transfer line -- 5 -- PARENT: Successfully pushed to child! CHILD: Received line -- 5 -- CHILD: error when trying to send(): send: Cannot determine peer addres +s at test_4.pl line 70. PARENT: closed writer, waiting for child to exit Could not connect! Died at test_4.pl line 58.
SERVER output:
server waiting for client connection on port 7775 connection from 127.0.0.1:37480 received data: -- 1 -- connection from 127.0.0.1:37480 received data: -- 2 -- connection from 127.0.0.1:37480 ^CCan't call method "close" on an undefined value at sim.pl line 8.

Replies are listed 'Best First'.
Re: IO:Socket::INET: Strategies against death of child processes on broken pipe
by Corion (Patriarch) on Oct 13, 2016 at 08:23 UTC

    If you are really running the actual server code you showed, your code would greatly benefit from

    use strict;

    because you have two or three variables confusingly named the same:

    $SIG{ 'TERM' } = $SIG{ 'INT' } = sub { $socket->close(); }; $SIG{ 'PIPE' } = 'IGNORE'; my $client_socket; # creating a listening socket my $socket = new IO::Socket::INET ( LocalHost => '0.0.0.0', LocalPort => $port, Proto => 'tcp', Listen => 5, Reuse => 1 );

    Either the declaration of $socket should be moved above your $SIG{ 'TERM' } = $SIG{ 'INT' } handlers, or the variable $socket is intended to be $client_socket, and then the declaration of $client_socket should be moved upwards.

    If you want to dissociate your process children from their parent, you might have to do something like Complete Dissociation of Child from Parent and/or the double fork.

      > If you want to dissociate your process children from their

      > parent, you might have to do something like Complete

      > Dissociation of Child from Parent and/or the double fork.

      Will this prevent the child being terminated by a broken pipe and, if so, why?

        I would expect the dissociated child to be immune from signals sent to the parent process.

        But given your code, I cannot see or reproduce where a Broken Pipe signal or error happens in either the child part or the parent part of the client program. Maybe somewhere in your real client program you do not properly catch a write failure to the parent process.

        While looking through your code, I found the overall interaction very confusing. The current image I have from your process setup is the following:

        server.pl <--- TCP :7777 --> client.pl <--- readpipe() + ---> client.pl forked child + forking parent $reader + $writer

        Maybe my impression of this is wrong; if so, please correct it.

        From that image, I think your real problem is not in the interaction between client.pl and server.pl, but in the interaction between the forked child of client.pl and the parent client.pl, which communicate by a pipe. And one of the sides seems to close the pipe prematurely such that the other end tries to interact with its end of the pipe and then receives a signal for trying that. But that interpretation is not really exercised by your test scenario which is "stopping the server at random". Maybe you can work on your client script or check better where the interactions with the pipe happen and put more diagnostics there to make it clearer (at least to you) where the signal actually is received and what might cause it.

        Note that I've tried your scripts on some random version of Linux only. Signals behave differently on different operating systems and vastly differ in the interaction with fork(), especially on Windows. So it might be of use if you state your type and version of OS.

        Your current scripts do not use strict, which makes it hard for me to judge whether the variables you use are actually the variables you intend to use. For example, the forked child seems to make a fresh connection to the server on every new line it wants to write to the server, and I'm not sure if that is intended or an artifact of weirdly scoped variables. If you could provide a version of your programs that has the variables properly and explicitly scoped so that it compiles and runs under use strict;, that would help me to help you better.

Re: IO:Socket::INET: Strategies against death of child processes on broken pipe
by tybalt89 (Monsignor) on Oct 13, 2016 at 15:00 UTC

    WAG: In your NDA code you have a close in the wrong place. Try commenting out all close statements in both the parent and child and see what happens.