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

Lo, perl 5.8.3. I have a server with only ever one client. The client talks to the server via named pipe. On the live machine the client is logging a write to the pipe, but the server isn't logging a read. This happens every so often, ie the majority of the time it works. Below are two scripts, I can't get these to fail. The server and client code are similar and are shown after. I think the problem may be related to server load, but I have no idea tbh. I have put in a SIG PIPE handler, but it never gets called and output stdout and stderr, but nothing is sent to them. The posix pipe buffer is 512bytes but the message is nowhere near this length. I'm sure I'm doing something fundamentally wrong, but don't know what. Any clue greatly appreciated. Here's the server. The sleep simulates the server doing something.
#! /usr/bin/perl -w use strict; use diagnostics; while (1) { print "HERE\n"; open FH, "<pipe"; my $data = <FH>; close FH; print "D: $data\n"; for ( split /ROSE/, $data ? $data : '' ) { if ( /^RELOAD/ ) { } else { sleep 3; } } } # while 1
And here's the test client
#! /usr/bin/perl -w use strict; use diagnostics; my $f = shift; for my $dir (1..20) { open (PIPE, ">pipe"); $dir = $f.'_'.$dir; $| = 1; select(PIPE); print STDOUT "$dir\n"; print PIPE "C,$dir,1ROSE"; close PIPE; sleep 1; }
Here's the live server code snippet:
open FH, "<$config->{'pipe'}"; my $data = <FH>; close FH; Log( { logfile => $log_file, text => 'trace: START: '. ( $data || +'$data no val' ) }) if TRACE;
and the live client code snippet:
if ( $has_files ) { my @now = gettimeofday; open FH, ">> /tmp/tmd_logs/DNOTIFY.LOG"; print FH format_time( $now[0] ).'.'.$now[1], " PREOPEN $dir\n"; close FH; open (PIPE, ">$pipe"); @now = gettimeofday; open FH, ">> /tmp/tmd_logs/DNOTIFY.LOG"; print FH format_time( $now[0] ).'.'.$now[1], " POSTOPEN $dir\n"; close FH; $| = 1; select(PIPE); eval { print PIPE "C,$dir,1ROSE"; }; $dirs{$dir}-- if $@; close PIPE; @now = gettimeofday; open FH, ">> /tmp/tmd_logs/DNOTIFY.LOG"; print FH format_time( $now[0] ).'.'.$now[1], " POSTCLOS $dir\n"; close FH; }
The logs show that the client "sent" a message at 08:40:20 and the server didn't get it ie it got one at 08:40:17 and another at 08:40:22 (the server log is GMT, client log BST) Here's a sample from the client log:
08:40:20.96244 PREOPEN /other/hosting/LIVE/127/out/3_304_127_21 08:40:20.96460 POSTOPEN /other/hosting/LIVE/127/out/3_304_127_21 08:40:20.96570 POSTCLOS /other/hosting/LIVE/127/out/3_304_127_21 08:40:20.96690 S: 14:35:51 H: 1 A: 08:31:06 M: 08:40:19 C: 08:40:19 SI +G: 0 /other/hosting/LIVE/127/out/3_304_127_21 08:53:38.319388 PREOPEN /other/hosting/LIVE/127/out/3_304_127_21 08:53:38.319599 POSTOPEN /other/hosting/LIVE/127/out/3_304_127_21 08:53:38.319712 POSTCLOS /other/hosting/LIVE/127/out/3_304_127_21 08:53:38.319834 S: 08:40:19 H: 1 A: 08:53:32 M: 08:53:36 C: 08:53:36 S +IG: 0 /other/hosting/LIVE/127/out/3_304_127_21 08:53:40.359719 S: 08:53:36 H: 0 A: 08:53:38 M: 08:53:38 C: 08:53:38 S +IG: 0 /other/hosting/LIVE/127/out/3_304_127_21
and here's the time snippet from the server log.
080411 07:40:17.514797 [8625] trace: START: C,/other/hosting/toxml/LIV +E/273/out/1_128_1,1ROSE 080411 07:40:22.248450 [8625] trace: START: C,/other/hosting/LIVE/251/ +out/3_128_251_3,1ROSE

Replies are listed 'Best First'.
Re: IPC via named pipes, losing some messages
by ikegami (Patriarch) on Apr 11, 2008 at 09:11 UTC

    $| = 1; select(PIPE);
    is wrong. It sets autoflush for the previously select handle, not PIPE. It should be
    my $old_sel = select(PIPE); $|=1; select($old_sel);
    or the more readable
    use IO::Handle qw( ); PIPE->autoflush(1);

    Now that won't fix your problem. Since you proceed to close the pipe after every print, everything is being flushed even though you fail to turn on autoflush. However, once you fix autoflush, you won't have any reason to keep reopening and closing the pipe.

    Now, I'm quite curious as to what you use <PIPE> to read from the pipe (with $/ left to its default) when the data sent over the pipe doesn't contains any newlines.

      I wan't sure about the autoflush, but I've tried it with and without all to no avail. Out of interest, wouldn't
      select(PIPE); $| = 1;
      work? fwiw as it's in a loop, wouldn't the second iteration turn it on?
      I digress...
      I don't understand your last paragraph. Could you explain it please?

        It would work, but print statements without a file handle will now go into the PIPE instead of to STDOUT. That's probably not what you want to do, and I don't like using select that way (spooky action at a distance). That's why you should have a second select to re-select the currently selected handle.

        I don't understand your last paragraph. Could you explain it please?

        <FILE> reads until end of line, but your data doesn't contain lines.

Re: IPC via named pipes, losing some messages
by cdarke (Prior) on Apr 11, 2008 at 10:37 UTC
    Are you sure another program is not reading the pipe? The named pipe files are sometimes treated as regular files by some (flawed) programs, for example backups. One way around that is to use /tmp, which doesn't generally get backed-up, although that can then create problems with an automatic tidy script!

    Another thought, does appending a "\n" help? (It shouldn't, since you have buffering turned off, but is there any NFS software involved?)
      Nothing else is reading the pipe. No backups are running. I haven't tried the \n, but will do in a bit as I'm clutchig at straws :) No nfs is involved. (The grandparent directory is exported, fwiw)
Re: IPC via named pipes, losing some messages
by pc88mxer (Vicar) on Apr 11, 2008 at 16:09 UTC
    I see a problem with repeatedly opening and closing the pipe and using buffered I/O:
    while (1) { ... open FH, "<pipe"; my $data = <FH>; close FH ... }
    I would open the pipe outside the while loop. When you using buffered I/O (ala <FH>) you can read more than one line from the pipe even though only one line is returned by <FH>. The rest is buffered by perl, but since you are closing the pipe right away it essentially is discarded. This could account for why the server is not logging all of its reads.
      Well with some ideas from above, I have tried:
      local $/; open PIPE, "<$pipe"; my $data = <PIPE>; close PIPE;
      Which didn't work, I'm still missing some messages. So, assuming the problem is with buffered IO, I thought I'd try:
      open FH, "<$config->{'pipe'}"; my $data; while (sysread FH, my $buf, 2048) { $data .= $buf; } close FH;
      Which is also not working. I am stumped. I could open the pipe out of the loop, but then it wouldn't block on the open and I'd rather not sleep to simulate that. Any ideas?
        I think you're better off using a socket based approach. Then you'll have a separate file handle for every connection. By closing and re-opening the pipe you introduce a race condition and (at the very least) run the risk of causing SIGPIPE signals to be sent to the client processes. (And maybe this is what is happening since your clients are logging the message being sent before writing it to the pipe.)

        That said, does this work for you?

        open FH, "<pipe" or die "open failed; $!\n"; my $buf; while (1) { my $nr = sysread(FH, $buf, 1024, length($buf)); while ($buf =~ s/^(.*?)\n//) { process_line($1); } sleep(1); } close(FH);
        I haven't yet found out how to make the sysread call blocking, but if I do I'll let you know.
Re: IPC via named pipes, losing some messages
by casiano (Pilgrim) on Apr 11, 2008 at 13:19 UTC
    Named pipes don't work if they are in a shared filesystem (i.e. NFS). Did you check that?

    Best

    Casiano

Re: IPC via named pipes, losing some messages
by pc88mxer (Vicar) on Apr 14, 2008 at 21:04 UTC
    Here's how to make the reads block so you don't have to constantly re-open the pipe:
    open(RW, "+<pipe") or die "unable to open pipe: $!\n"; open(FH, "<pipe") or die "open pipe: $!\n"; while (<FH>) { # process line }
    The trick is to always have the pipe opened for writing so that you'll never get EOF when reading it.
      Thanks everyone for your help. I finally cracked it yesterday. The trick is to open the pipe for read/write, bypass perlio and have a blocking read. The code below shows how it's done. I could possibly have moved to sockets, but pipes are as old as the hills so it should be possible to use them and on top of that the writer could be a bash script. Having said that, I did find out how to have bash write to a socket...
      # Non blocking open open (FH, "+<", $config->{'pipe'} ) || die "Unable to open $config->{' +pipe'}"; my $rin = ''; my $rout = ''; vec( $rin, fileno(FH), 1) = 1; # No, I have no idea either # Loop forever. while (1) { # Wait for the pipe to change status my $nfound = select( $rout = $rin, undef, undef, undef ); # blocki +ng select my $data; # Non blocking select only returns a value while there is somethin +g in the pipe while ( select( $rout = $rin, undef, undef, 0 ) ) { sysread FH, my $buf, 2048; $data .= $buf; } Log( { logfile => $log_file, text => 'trace: START: '. ( $data || +'$data no val' ) }) if TRACE; # 9/4/8 A request is C,DIR,1ROSE # Multiple requests can come through on one read ie C,FOO,1ROSEC,B +AR,1ROSE for ( split /ROSE/, $data ? $data : '' ) { if ( /^RELOAD/ ) { # Reread the config file Log({logfile=>$log_file, text=> qq{RELOAD: Reloading confi +g}}); load_config(); } else { process_input( { data => $_ } ) if $_; } } } # while 1

        "ROSE" has got to be the weirdest line ending I've ever seen.

        I took the liberty of cleaning up your code a bit.

        • I fixed a bug where a single message could be considered two.
        • I eliminated the select which needlessly served to empty the pipe.
        • I replaced split since you have a record terminator rather than a record separator.
        # Non blocking open open (FH, "+<", $config->{'pipe'} ) || die "Unable to open $config->{' +pipe'}"; my $buf = ''; # Loop forever. while (1) { my $rv = sysread( FH, $buf, 2048, length($buf) ); die("Error reading from pipe: $!\n") if !defined($rv); # 9/4/8 A request is C,DIR,1ROSE # Multiple requests can come through on one read ie C,FOO,1ROSEC,B +AR,1ROSE while ($buf =~ s/(.*?ROSE)//s) { my $rec = $1; Log( { logfile => $log_file, text => "trace: START: $rec" }) if + TRACE; if ( $rec =~ /^RELOAD,/ ) { # Reread the config file Log({logfile=>$log_file, text=> qq{RELOAD: Reloading confi +g}}); load_config(); } else { process_input( { data => $rec } ); } } } # while 1