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

Perl Monks, any help will be much appreciated....I've got a server that use IO::Pipe to open up filehandles to forked sub processes and then loaded into an IO::Select object for reading. The issue I'm having is that filehandles generated by IO::Pipe do not seem to be handled properly by the can_read() method of IO::Select when the can_read is the condition in a while loop. The code below will read the first filehandle made available, however the while loop then exits, even with two filehandles still in the select set (as confirmed by the count() method).

This code was working using open "-|" and STDOUT, but debugging messages in sub-modules forced the need to use explicit filehandles in the child process and I converted to IO::Pipe. Please note that this is a simplified test version of the production application, that's why it doesn't make real world sense. This is being run on Solaris 2.8 on a standard install of Perl 5.6.1. Also, read_line() is a homegrown sysread() wrapper I wrote. Thanks in advance! Brian Goad

use IO::Select; use IO::Pipe; use IO::Handle; @connect = ("3","2","1"); # "main" child my $select_ch = new IO::Select; foreach $msg (@connect) { # for each write, fork a child if($msg) { # fork and open child for reading my $pipe = new IO::Pipe; if(my $pid = fork() ) { #parent $fh = $pipe->reader(); $debug && print "$$ forked $pid\n"; $fh->blocking(0); # set non-blocking I/O $debug && print "adding filehandle ($fh)\n"; $select_ch->add($fh); } else { #child my $childhandle = $pipe->writer(); sleep $msg; print $childhandle "$msg,response,$mt\n"; exit 0; #kill the child process } } } # done with processing this read # here we read for responses from the filehandles my @readyfiles; #array for select to populate my $rh; # a readable filehandle while( @readyfiles = $select_ch->can_read() ) { foreach $rh (@readyfiles) { # read from filehandle, send back string my $child_resp_string = ''; print "calling read_line\n"; my $bytes = read_line(\$child_resp_string,$rh); $debug && print "returned $child_resp_string\n"; # only one read per handle, so close $select_ch->remove($rh); $rh->close; my $cnt = $select_ch->count(); $debug && print "$cnt handles left to read\n"; } } print "exiting...\n"; exit 0;

Replies are listed 'Best First'.
Re: IO::Pipe and IO::Select
by leriksen (Curate) on Mar 02, 2004 at 05:02 UTC
    Seems fine to me, given that I cant see the all important read_line sub

    Here's my version, with some important revisions

    • shebang line with -w
    • use strict; to catch stupid errors
    • IO::Handle provides a very nice getline() method, so I used that instead of trying to interpolate what your read_line does. When doing IO, almost anything you write will be already done better and faster by someone else - if I find myself wrapping sysread or such like, I only do so because I know that is what is required. If I'm guessing, I look for a builtin.
    • I made it a little more obvious who was reading and writing in the debugs. Probably better if you write to STDERR, as that isn't buffered by default.
    #!/usr/bin/perl -w use strict; use IO::Select; use IO::Pipe; use IO::Handle; my $debug = 1; my @connect = ("3","2","1"); my %msgs; @msgs{@connect} = qw/hooray hip hip/; my $select_ch = IO::Select->new(); foreach my $msg (@connect) { # for each write, fork a child if ($msg) { # fork and open child for reading my $pipe = IO::Pipe->new(); if (my $pid = fork() ) { #parent my $fh = $pipe->reader(); $debug && print "parent $$ forked $pid\n"; $fh->blocking(0); # set non-blocking I/O $debug && print "parent adding readable filehandle ($fh)\n"; $select_ch->add($fh); } else { #child my $childhandle = $pipe->writer(); my $mt = $msgs{$msg}; sleep $msg; print STDERR "$$ child $msg writing to pipe $msg, response $mt +\n"; print $childhandle "child $msg, response $mt"; exit 0; #kill the child process } } } # done with processing this read # here we read for responses from the filehandles my @readyfiles; #array for select to populate while ( @readyfiles = $select_ch->can_read() ) { foreach my $rh (@readyfiles) { # read from filehandle, send back string print "$$ calling IO::Handle::getline\n"; my $child_resp_string = $rh->getline(); $debug && print "$$ read |$child_resp_string|\n"; # only one read per handle, so close $debug && print "$$ removing @{[ref $rh]}\n"; $select_ch->remove($rh); $rh->close; $debug && print "$$ @{[$select_ch->count()]} handles left to read\ +n"; } } print "exiting...\n"; exit 0;

    Platforms
    Linux itdevtst 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 i686 i686 i386 GNU/Linux
    SunOS robin 5.8 Generic sun4u sparc SUNW,Ultra-60

    Perl versions
    This is perl, v5.8.2 built for i686-linux
    This is perl, v5.8.1 built for sun4-solaris

    Output

    le99007@robin:~/src/perl> ./pipe.pl parent 7146 forked 7147 parent adding readable filehandle (IO::Pipe::End=GLOB(0x195708)) parent 7146 forked 7148 parent adding readable filehandle (IO::Pipe::End=GLOB(0x1957a4)) parent 7146 forked 7149 parent adding readable filehandle (IO::Pipe::End=GLOB(0x151eb0)) 7149 child 1 writing to pipe 1, response hip 7146 calling IO::Handle::getline 7146 read |child 1, response hip| 7146 removing IO::Pipe::End 7146 2 handles left to read 7148 child 2 writing to pipe 2, response hip 7146 calling IO::Handle::getline 7146 read |child 2, response hip| 7146 removing IO::Pipe::End 7146 1 handles left to read 7147 child 3 writing to pipe 3, response hooray 7146 calling IO::Handle::getline 7146 read |child 3, response hooray| 7146 removing IO::Pipe::End 7146 0 handles left to read exiting...

    Same result on the Linux box

    +++++++++++++++++
    #!/usr/bin/perl
    use warnings;use strict;use brain;

      Thanks for the help! Strangely enough, when I copied the code I had posted (which was an edited version of my full test code), it worked correctly. Still not sure why my first version was failing.

      I had avoided $pipe->getline($fh) because the documentation said it behaved like <$fh> and I was under the impression that select (even IO::Select) and <> were incompatible. Now that I think about it, behaving like <> and actually being <> are two different things. So, now I've converted my production code to use IO::Pipe and now have a whole new set of problems :)

      Thanks again for your assitance.

Re: IO::Pipe and IO::Select
by jmccarrell (Novice) on Aug 05, 2006 at 18:52 UTC
    Thanks very much for this; it helps to see some simple working code. One minor nit is that IO::Handle doesn't need to be used here, just IO::Select and IO::Pipe.