in reply to Non-blocking I/O woes
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; }
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^2: Non-blocking I/O woes
by ikegami (Patriarch) on Jan 11, 2011 at 05:58 UTC | |
by juster (Friar) on Jan 11, 2011 at 15:41 UTC | |
by ikegami (Patriarch) on Jan 11, 2011 at 19:04 UTC | |
|
Re^2: Non-blocking I/O woes
by ikegami (Patriarch) on Jan 11, 2011 at 05:51 UTC | |
|
Re^2: Non-blocking I/O woes
by dwalin (Monk) on Jan 12, 2011 at 16:23 UTC |