http://www.perlmonks.org?node_id=534791

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

Fellow monks, I'm painfully confused by an apparent broken interaction between IO::Pipe and Net::Server(::Fork). I have a program which started life as a standalone command-line tool. At its heart, it created a new IO::Pipe and used it as $pipe->reader('command', 'arg'); I needed to change the standalone tool to be a socketed server and picked Net::Server::Fork since that seemed like a good fit. Now, a server child process collects all sorts of stuff from the connected client and then proceeds to run a wrapped version of what started in the stadalone tool. However, in the standalone version, I could do:
$pipe->reader('command', 'arg'); my $line = scalar(<$pipe>);
and I'd get data. In the Net::Server/wrapped version, I don't get anything from the scalar(<$pipe>); call and, instead, the output of 'command' is dumped on the server's STDERR. Has anyone seen this behavior or have an idea of what could be broken?

Replies are listed 'Best First'.
Re: (Seemingly) Broken interactions between Net::Server and IO::Pipe? (dup)
by tye (Sage) on Mar 08, 2006 at 16:41 UTC

    I couldn't find it actually documented that Net::Server redirects STDIN and STDOUT as you appear to expect it to. It is implied by the documentation but I saw no actual mention of this fact, though it was rather a quick scan of the documentation. I was expecting to find something quite explicit on that point.

    Rummaging through the code I find:

    *STDIN = \*{ $prop->{client} }; *STDOUT = \*{ $prop->{client} } if ! $prop->{client}->isa('IO::Soc +ket::SSL'); # ... select(STDOUT);

    Which is a pretty lousy way to do something like this. Copying file handles via symbol table glob manipulations is a pretty fundamentally broken technique in my experience. I've found it to be buggy. More importantly, it violates important properties of file handles and file descriptors.

    If you want to "duplicate some handles" (as a comment in the code notes), then you should dup() them, which is done as documented in open: open STDIN, "<&" . fileno($prop->{client}) ..., for example; or use "<&=" if you just want to fdopen() rather than dup().

    Of course, that would interfere with "magical" file handles (such a tied ones), so the code should probably look more like:

    my $fileno= fileno $prop->{client}; if( defined $fileno ) { open STDIN, "<&$fileno" or die ...; open STDOUT, ">&$fileno" or die ...; } else { close STDIN; close STDOUT; *STDIN= \*{ $prop->{client} }; *STDOUT= \*{ $prop->{client} }; }

    Note that I close STDIN and STDOUT before I overwrite them, as my experience is that the relation between glob life cycle and file handle life cycle is buggy.

    Unfortunately, none of this looks to me like a "smoking gun" to explain your particular problem. But you could try patching Net/Server.pm and see if it makes a difference.

    If I were having this problem, then I'd debug the server and watch what sockets gets created when a connection is opened and what STDOUT gets set to before my method gets called.

    I suspect that you could even stop using IO::Pipe and you'd still have this problem.

    - tye        

      Ooooh... That actually seems to fix it! Yippeee!!! Thanks so much, tye. Now to go find the author of Net::Server and convince him that he needs to patch it.
        This patch and many others are now in 0.91 on CPAN.

        Thanks tye.

        my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: (Seemingly) Broken interactions between Net::Server and IO::Pipe?
by bmcatt (Friar) on Mar 07, 2006 at 14:38 UTC
    Slimmed down my code to an (obvious) test case... which (thankfully) fails.
    #!/usr/bin/perl -w # server package Server; use strict; use base qw(Net::Server::Fork); my $server = bless { server => { port => 8097, log_level => 4, }, }, 'Server'; $server->run(); exit; sub process_request { my $self = shift; my $x = Feeder->new(); my $line = $x->get_line(); print "Line = $line\n"; } package Feeder; use strict; use IO::Pipe; sub new { my $class = shift; my $pipe = IO::Pipe->new(); $pipe->reader('cat', '/home/ben/foo/client'); my $self = { PIPE => $pipe, }; return bless $self, $class; } sub get_line { my $self = shift; my $fh = $self->{PIPE}; return scalar(<$fh>); }
    And, the corresponding client,
    #!/usr/bin/perl -w # client use strict; use IO::Socket::INET; my $sock = IO::Socket::INET->new(PeerHost => 'localhost', PeerPort => 8097, Proto => 'tcp', ); die "Unable to connect" unless $sock->connected(); while (<$sock>) { print "Got: >$_<\n"; }
    I can't see that there's anything wrong with any of the above, although I'd love to be convinced otherwise...
Re: (Seemingly) Broken interactions between Net::Server and IO::Pipe?
by tcf03 (Deacon) on Mar 07, 2006 at 19:42 UTC
    I get the following from that code
    2006/03/07-14:16:21 CONNECT TCP Peer: "127.0.0.1:36018" Local: "127.0. +0.1:8097" testing! Use of uninitialized value in concatenation (.) or string at server.pl + line 23. Got: >Line = <
    Looks like its doing what its supposed to. What version of Perl? Im using 5.8.5 under Linux.

    UPDATE:
    Or when connecting from a remote machine:
    Got: >Line = <


    Ted
    --
    "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
      --Ralph Waldo Emerson
      Nope, what you're seeing is exactly the broken behavior that I mentioned. This section:
      sub process_request { my $self = shift; my $x = Feeder->new(); my $line = $x->get_line(); print "Line = $line\n"; }
      should be reading the first line from the client file, so $line should be #!/usr/bin/perl -w. The fact that it's not set to anything means that the pipe's not working. I'm running with 5.8.8 under Linux as well. IO::Pipe is identifying itself as version 1.13 and Net::Server is version 0.90.
        I have never used IO::Pipe before, but from the docs it looks like you need to do a
        while(<$pipe>) { read from pipe... }
        when I run it it cats out the file fine, Id probably replace IO::Pipe with a routine that reads the file one line at a time. At least from what Ive read, that seems to be the desired effect.

        UPDATE
        BTW, when running this code on Cygwin, I get the following:
        ted@skywalkerii ~ $ perl client.pl 2006/03/08-02:31:02 CONNECT TCP Peer: "127.0.0.1:1245" Local: "127.0.0 +.1:8097" Use of uninitialized value in concatenation (.) or string at server.pl + line 23. Got: >Line = < #!/usr/bin/perl -w # client use strict; use IO::Socket::INET; my $sock = IO::Socket::INET->new(PeerHost => 'localhost', PeerPort => 8097, Proto => 'tcp', ); die "Unable to connect" unless $sock->connected(); while (my $receive = <$sock>) { print "Got: >$receive<\n"; }
        after copying client.pl to /home/ted/foo/client

        Ted
        --
        "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
          --Ralph Waldo Emerson