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

I'm quite sure this is something very dumb, but one of my small programs just exits when the other end of a TCP connection is terminated. Let's have a look parts of the code:
my $sock = eval { IO::Socket::INET->new( PeerAddr => $host, PeerPort => $port, Proto => 'tcp', ) or die $@; }; # ... my $lw = syswrite($sock, $$data); #line 91 die $! unless defined $lw and $lw == length($$data);
This are two snippets, each is also burried in a couple of eval()s. As a target I connect to "nc -l -p 1234". As soon as I kill the nc (ctrl-c), the next syswrite() will exit the program without any message. I even ran a trace:
... 1122277564.69174 main /root/iosrv.pl 91 1122277564.69182 main /root/iosrv.pl 92 1122277564.69185 main /root/iosrv.pl 93 1122277564.69188 main /root/iosrv.pl 95 1122277564.6919 main /root/iosrv.pl 100 1122277564.69193 main /root/iosrv.pl 39 1122277574.70139 main /root/iosrv.pl 36 1122277574.70156 main /root/iosrv.pl 37 1122277574.70159 main /root/iosrv.pl 38 1122277574.70162 main /root/iosrv.pl 85 1122277574.70165 main /root/iosrv.pl 86 1122277574.70167 main /root/iosrv.pl 88 1122277574.7017 main /root/iosrv.pl 89 1122277574.70172 main /root/iosrv.pl 90 1122277574.70174 main /root/iosrv.pl 91
Exactly what I'd expect, exept that the program just ends there. (line 91 is the syswrite())

I also tried to use print() instead of syswrite(), same problem. I tried with "v5.8.4 built for i686-linux" (self compiled) and "v5.6.1 built for i386-linux" (debian woody).

Any idea what the problem might be?


Search, Ask, Know

Replies are listed 'Best First'.
Re: unexpected program abort on write on closed handle
by eyepopslikeamosquito (Archbishop) on Jul 25, 2005 at 08:38 UTC

    If you try running your test program via the Linux strace command, i.e.:

    strace perl iosrv.pl
    that should shed more light on what is going on.

      Thank you.

      First, I constructed a smallest possible failing code:

      #!/usr/local/bin/perl8 use 5.006001; use strict; use warnings; use IO::Socket; my $sock = undef; eval { while (1) { outsend(scalar(localtime()) . "\n"); sleep 5; } }; if (my $e = $@) { die "Exception: $e"; } sub outsend { my $data = shift; $sock ||= IO::Socket::INET->new( PeerAddr => '127.0.0.1', PeerPort => 5555, Proto => 'tcp', ) || die $!; my $lw = syswrite($sock, $data); die $! unless defined $lw and $lw == length($data); }
      So, here's the strace:
      nanosleep({5, 0}, {5, 0}) = 0 time([1122281453]) = 1122281453 time([1122281453]) = 1122281453 write(3, "Mon Jul 25 10:50:53 2005\n", 25) = 25 time([1122281453]) = 1122281453 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({5, 0}, {5, 0}) = 0 time([1122281458]) = 1122281458 time([1122281458]) = 1122281458 write(3, "Mon Jul 25 10:50:58 2005\n", 25) = -1 EPIPE (Broken pipe) --- SIGPIPE (Broken pipe) --- +++ killed by SIGPIPE +++
      A SIGPIPE? What? I'm not quite sure, but I would not expect to have to handle that myself on a TCP connection. Or on any IO operation, I'd say.

      Ok, now the question is---what do I put into the %SIG-handler?


      Search, Ask, Know

        Ok, now the question is---what do I put into the %SIG-handler?
        For SIGPIPE, perhaps the most common technique (in both Perl and C) is to ignore it and then test the return code of the write, for example:
        use Errno ':POSIX'; $SIG{PIPE} = 'IGNORE'; for (1 .. 3) { unless (print SOMEPIPE "blah") { last if $! == EPIPE; # terminate the loop if reader goes awa +y die "I/O error: $!"; # this is more serious, so die } }

        If you use 4 arg select (or IO::Select) before you write to your socket then you can avoid getting SIGPIPE for a relatively unsurprising condition like "the reading end is dead".
Re: unexpected program abort on write on closed handle
by anonymized user 468275 (Curate) on Jul 25, 2005 at 09:16 UTC
    A broken pipe does indeed send a fatal signal to your program. If you want the behaviour to be otherwise, there are a couple of decisions you first might have to make:

    Firstly, whether to handle the signal as a warning or an error. For example, you could assign $SIG{ PIPE } to a handler that treats it as a warning, e.g.

    $SIG{ PIPE } = sub { warn "Broken pipe trap, traceback follows:\n" . T +race::trace(); }
    The second decision of course being whether to use Devel::Trace as would be necessary for the above example, so that the signal handler reports where in the code it was invoked.

    One world, one people

      I'd like the syswrite() to simply return undef, nothing more. My existing code then will throw away the socket and try to (re-)connect to any available server.
      TRY: for (1 .. $maxretries) { eval { $sock ||= outconnect() || die $diag=$!; my $lw = syswrite($sock, $$data); die $! unless defined $lw and $lw == length($$data); $$data = ''; }; if (my $e = $@) { $diag ||= $e; $sock = undef; next TRY;
      PS: Setting it to 'IGNORE' works just fine: Exception: Broken pipe at /root/iotest.pl line 31.

      Search, Ask, Know
        You are of course right, that it can be made to be ignored. I forgot that because I am used to project standards that require all fatal conditions to at the least be written to a log file for review by production support personnel, even though this often leads to huge logfiles that need to be regularly archived off!

        One world, one people