1

I found this example (posted by @ikegami) of a way to use IPC::Open3 on windows using sockets. The problem is that, when I run it, I get an error An existing connection was forcibly closed by the remote host on the sysread. The command runs, the select works correctly, but the sysread is getting an undef instead of the expected 0 for end of file. This behavior is not the same for all commands. If I change the command to echo Hello World! it does not cause the error. Any idea what is going on here?

Here is the code from the example:

use strict;
use warnings;

use IO::Select qw( );
use IPC::Open3 qw( open3 );
use Socket     qw( AF_UNIX SOCK_STREAM PF_UNSPEC );

print( "REMOVE ME: getting started\n" );

sub _pipe {
    socketpair($_[0], $_[1], AF_UNIX, SOCK_STREAM, PF_UNSPEC)
        or return undef;
    shutdown($_[0], 1);  # No more writing for reader
    shutdown($_[1], 0);  # No more reading for writer
    return 1;
}

sub _open3 {
    local (*TO_CHLD_R,     *TO_CHLD_W);
    local (*FR_CHLD_R,     *FR_CHLD_W);
    local (*FR_CHLD_ERR_R, *FR_CHLD_ERR_W);

    if ($^O =~ /Win32/) {
        _pipe(*TO_CHLD_R,     *TO_CHLD_W    ) or die $^E;
        _pipe(*FR_CHLD_R,     *FR_CHLD_W    ) or die $^E;
        _pipe(*FR_CHLD_ERR_R, *FR_CHLD_ERR_W) or die $^E;
    } else {
        pipe(*TO_CHLD_R,     *TO_CHLD_W    ) or die $!;
        pipe(*FR_CHLD_R,     *FR_CHLD_W    ) or die $!;
        pipe(*FR_CHLD_ERR_R, *FR_CHLD_ERR_W) or die $!;
    }

    my $pid = open3('>&TO_CHLD_R', '<&FR_CHLD_W', '<&FR_CHLD_ERR_W', @_);

    return ( $pid, *TO_CHLD_W, *FR_CHLD_R, *FR_CHLD_ERR_R );
}

# when i change the command to 'echo Hello World' it works...
my ($pid, $to_chld, $fr_chld, $fr_chld_err) =
    _open3('cmd /c "dir /s/b"');

my %objs;

my $in_sel  = IO::Select->new();
my $out_sel = IO::Select->new();

for my $fh ($fr_chld, $fr_chld_err) {
    my $obj = {
        buf => '',
    };
    $objs{ fileno($fh) } = $obj;
    $in_sel->add($fh);
}

close($to_chld);

while ($in_sel->count() + $out_sel->count()) {
    my ($ins, $outs) = IO::Select::select($in_sel, $out_sel, undef);

    for my $fh (@$ins) {
        my $obj = $objs{ fileno($fh) };
        our $buf; local *buf = \( $obj->{buf} );
        my $bytes_read = sysread($fh, $buf, 64*1024, length($buf));
        if (!$bytes_read) {
            warn("Error reading from child: $!\n")
                if !defined($bytes_read);
            $in_sel->remove($fh);
        }
    }

    for my $fh (@$outs) {
    }
}

waitpid($pid, 0);

print("STDOUT:\n$objs{ fileno( $fr_chld     ) }{buf}");
print("\n" );
print("STDERR:\n$objs{ fileno( $fr_chld_err ) }{buf}");
Lucas
  • 14,227
  • 9
  • 74
  • 124

1 Answers1

1

I think it's because something like shutdown was used instead of something like close. Sounds safe to ignore.

grep $!{$_}, keys %! shows ECONNRESET, so just change

warn("Error reading from child: $!\n")
    if !defined($bytes_read);

to

warn("Error reading from child: $!\n")
    if !defined($bytes_read) && !$!{ECONNRESET};
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thanks, not sure i fully understand why it would be happening though (especially since it appears to only be some commands). But this worked. Out of curiosity, I cant help but notice that you are the same ikegami that posted the original code on perlmonks in 2009... Is that site still a good place for perl questions, or do the monks now spend more time on the SO? – Lucas May 21 '13 at 19:43
  • Don't know exactly why either, beyond "different programs are free to do different things". Both are great places. – ikegami May 21 '13 at 19:45
  • In case you care, I leveraged the knowledge from this to put together a library for running IPC::Open3 that works on windows and unix. You can take a look here if you are curious: https://github.com/lucastheisen/ipc-open3-callback. I'd like to add it to CPAN but they guy who owns `IPC::Open3` has not responded to my request to expand on his namespace... – Lucas May 21 '13 at 19:50
  • That's pretty cool, though keep in mind it can already be done with awesome modules IPC::Run3 and IPC::Run. IPC::Open3 is a core module "owned" by the Perl5 Porters (the developers of Perl itself), so contact `perl5-porters@perl.org`. – ikegami May 21 '13 at 19:57
  • I have seen `IPC::Run`, and `IPC::Run3`. I wanted to use them (_true CPAN believer here_), but couldn't make it fit my use case. Basically we have an automation library that runs commands on remote systems. Things like `ssh automation@devserver tail -f some.log`. With IPC::Run, i can store the output in a var or send it to a handle, but i need to perform an action each time data becomes available (print locally). Of course i could get handles and use select, but that is basically what this library does. Do you think that provides enough utility to be its own module? – Lucas May 21 '13 at 20:41
  • With IPC::Run and IPC::Run3, you can do EXACTLY what your module does. To replicate the example in your Synopsis with IPC::Run: `run(['sh', '-c', 'Hello World'], '>', sub { print( "$_[0]\n" ); }, '2>', sub { print( STDERR "$_[0]\n" ); });` – ikegami May 22 '13 at 01:47
  • DANG. It says it right in the doc, i just missed it. Was looking for `sub {}` but it says `\&sub`. You are right and i greatly appreciate all of your comments and help. It has been a great learning experience. Monk you are! – Lucas May 22 '13 at 16:05
  • IPC::Run appears not to be capable of `sub` for `out` or `err` on windows: `croak "Can't spawn a subroutine on Win32" if Win32_MODE && ref eq "CODE";`... – Lucas May 22 '13 at 18:34