I think $select->can_read() is blocking but not in the way you are expecting. can_read will return a handle when the end of file is reached as well. A feature I did not expect is that can_read will return an empty list, the blocking abruptly stopped, when a signal is caught by a signal handler. This means, after the SIGCHLD handler is executed the code execution returns from can_read with an empty list. This empty list means the end of your while loop and script.

Inside your spawn sub, when you are forking by using open you are not exiting the forked child process. The child will merrily return from the spawn sub and act as a proud new parent. Two forked processes reading the same STDIN is a recipe for bizarre behavior.

I think it would also be easier to use open to call the "script" program directly instead of forking and then calling the script with exec. If you use open to call the external program then you don't need to worry about exiting the forked process after exec.

Here is a version I messed with tonight. IPC is always interesting...

#!/usr/bin/env perl use warnings; use strict; use IO::Handle qw(); use IO::Select qw(); use File::Temp qw(tempfile); #--------------------------------------------------------------------- +------- # CHILDREN my $CHILD_SCRIPT_PATH = './child.pl'; my $Select_inputs = IO::Select->new(); my %Child_of_handle; sub spawn_child { my ( $script_input ) = @_; my ( $tempfh, $tempname ) = tempfile(); print $tempfh $script_input; close $tempfh; # flush to disk my $pid = open my $kidoutput, "$CHILD_SCRIPT_PATH <$tempname |"; die "fork failed: $!" unless defined $pid; # Turns off buffering as well? Without this the child's # output comes all at once when it exits. $kidoutput->blocking( 0 ); $Child_of_handle{ $kidoutput->fileno } = [ $pid, $tempname ]; $Select_inputs->add( $kidoutput ); return; } # Kill all child processes. sub kill_procs { warn "KILLING CHILD PROCESSES\n"; kill 9, $_->[0] for values %Child_of_handle; } # Cleanup data and temporary input file associated with the child's pi +pe handle. sub cleanup_child { my ( $handle ) = @_; my $child_ref = $Child_of_handle{ $handle->fileno } or die "Failed to lookup child handle $handle"; my ( $pid, $tempfile ) = @$child_ref; delete $Child_of_handle{ $handle->fileno }; $Select_inputs->remove( $handle ); unlink $tempfile or warn "Failed to delete temp file $tempfile:\n$ +!"; return; } #--------------------------------------------------------------------- +------- # COMMANDS / PROMPT my ( %Commands, $Prompt ); sub prompt_state { my ( $input ) = @_; my ( $first, @args ) = split /\s+/, $input; unless ( $first ) { warn sprintf "Please enter a command: %s\n", join q{, }, keys %Commands; return; } my $cmd = $Commands{ $first }; unless ( $cmd ) { warn "Unknown command: $first\n"; return; } # I don't use arguments but you see it is possible. $cmd->( @args ); return; } my $run_input = q{}; sub run_state { my ( $input ) = @_; unless ( $input eq q{.} ) { $run_input .= $input . qq{\n}; print q{>}; return; } $Prompt = \&prompt_state; unless ( $run_input ) { warn "IGNORING EMPTY INPUT\n"; return; } spawn_child( $run_input ); $run_input = q{}; return; } %Commands = ( 'run' => sub { $Prompt = \&run_state; print q{>}; }, 'exit' => sub { kill_procs(); exit 0; }, ); #--------------------------------------------------------------------- +------- # MAIN LOOP # STDIN fileno is 0 sub STDIN_NUM() { 0 } STDOUT->autoflush( 1 ); # for the run prompt's > to display properly.. +. $Select_inputs->add( \*STDIN ); $Prompt = \&prompt_state; $SIG{'INT' } = sub { kill_procs(); exit 1; }; $SIG{'CHLD'} = 'IGNORE'; SELECT_LOOP: while ( 1 ) { my @ready = $Select_inputs->can_read or die 'select returned an empty list'; # User input is read from the prompt... if ( grep { $_->fileno == STDIN_NUM } @ready ) { # STDIN is line-buffered my $line = <STDIN> || q{}; chomp $line; $Prompt->( $line ); next SELECT_LOOP; } # Child process input (this is output from the child's perspective +) my $next = shift @ready; if ( $next->eof ) { # If EOF is reached we are done with this child process. cleanup_child( $next ); next SELECT_LOOP; } my $pid = $Child_of_handle{ $next->fileno }[0]; my $output = do { local $/; <$next> }; # slurp! print map { "$pid:$_\n" } split /\n/, $output; }
edit: wrapped code in a readmore tag

In reply to Re: Non-blocking I/O woes by juster
in thread Non-blocking I/O woes by dwalin

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.