--- AnyEvent-Fork-Remote/Remote.pm 2013/04/27 01:44:55 1.1 +++ AnyEvent-Fork-Remote/Remote.pm 2013/04/28 03:46:34 1.4 @@ -18,7 +18,7 @@ =head1 DESCRIPTION -Despite what the name of this module might suggest, it doesn't actualyl +Despite what the name of this module might suggest, it doesn't actually create remote processes for you. But it does make it easy to use them, once you have started them. @@ -57,6 +57,7 @@ use common::sense; +use Carp (); use Errno (); use AnyEvent (); @@ -104,6 +105,14 @@ otherhost exec perl); +=item my $proc = new_execp AnyEvent::Fork::Remote $file, @args... + +Just like C, except that the program is searched in the +C<$ENV{PATH}> first, similarly to how the shell does it. This makes it easier +to find e.g. C: + + $proc = new_execp AnyEvent::Fork::Remote "ssh", "ssh", "otherhost", "perl"; + =item my $proc = new AnyEvent::Fork::Remote $create_callback Basically the same as C, but instead of a hardcoded command @@ -140,9 +149,39 @@ $done->($a); }; +=item my $proc = new_from_fh $fh + +Creates an C object from a file handle. This file +handle must be connected to both STDIN and STDOUT of a F process. + +This form might be more convenient than C or C when +creating an C object, but the resulting object +does not support C. + =cut -sub new_exec { +sub new { + my ($class, $create) = @_; + + bless [ + $create, + "", + [], + ], $class +} + +sub new_from_fh { + my ($class, @fh) = @_; + + $class->new (sub { + shift @fh + or Carp::croak "AnyEvent::Fork::Remote::new_from_fh does not support fork"; + }); +} + +sub _new_exec { + my $p = pop; + my ($class, $program, @argv) = @_; require AnyEvent::Util; @@ -160,7 +199,8 @@ open STDIN , "<&" . fileno $b or die; open STDOUT, ">&" . fileno $b or die; - Proc::FastSpawn::spawn ($program, \@argv); + $p ? Proc::FastSpawn::spawnp ($program, \@argv) + : Proc::FastSpawn::spawn ($program, \@argv); open STDIN , "<&" . fileno $oldin ; open STDOUT, ">&" . fileno $oldout; @@ -169,14 +209,14 @@ }) } -sub new { - my ($class, $create) = @_; +sub new_exec { + push @_, 0; + &_new_exec +} - bless [ - $create, - "", - [], - ], $class +sub new_execp { + push @_, 1; + &_new_exec } =item $new_proc = $proc->fork @@ -214,8 +254,7 @@ =cut sub send_fh { - require Carp; - Carp::croak ("send_fh is not supported on AnyEvent::Fork::Remote objects"); + Carp::croak "send_fh is not supported on AnyEvent::Fork::Remote objects"; } =item $proc = $proc->eval ($perlcode, @args) @@ -243,7 +282,12 @@ sub eval { my ($self, $perlcode, @args) = @_; - $self->[1] .= '{ local @_ = ' . (aq @args) . "; $perlcode }\n"; + my $linecode = $perlcode; + $linecode =~ s/\s+/ /g; # takes care of \n + $linecode =~ s/"/''/g; + substr $linecode, 70, length $linecode, "..." if length $linecode > 70; + + $self->[1] .= '{ local @_ = ' . (aq @args) . ";\n#line 1 \"'$linecode'\"\n$perlcode;\n}\n"; } =item $proc = $proc->require ($module, ...) @@ -255,8 +299,8 @@ sub require { my ($self, @modules) = @_; - s%::%/%g for @modules; - $self->eval ('require "$_.pm" for @_', @modules); + $self->eval ("require $_") + for @modules; $self } @@ -279,18 +323,24 @@ Very similar to the run method of L. -On the parent side, the API is identical. On the child side, the -"communications socket" is in fact just C<*STDIN>, and typically can only -be read from. - -If the run function wants to read data that is written to C<$fh> in the -parent, then it should read from STDIN. If the run function wants to -provide data that can later be read from C<$fh>, then it should write them -to STDOUT. - -You can write a run function that works with both L and -this module by checking C in on the passed callback in the run -function: +On the parent side, the API is identical, except that a C<$cb> argument of +C instad of a valid file handle signals an error. + +On the child side, the "communications socket" is in fact just C<*STDIN>, +and typically can only be read from (this highly depends on how the +program is created - if you just run F locally, it will work for +both reading and writing, but commands such as F or F typically +only provide read-only handles for STDIN). + +To be portable, if the run function wants to read data that is written to +C<$fh> in the parent, then it should read from STDIN. If the run function +wants to provide data that can later be read from C<$fh>, then it should +write them to STDOUT. + +You can write a run function that works with both L +and this module by checking C. If it is C<0> (meaning +it is STDIN), then you should use it for reading, and STDOUT for +writing. Otherwise, you should use the file handle for both: sub run { my ($rfh, ...) = @_; @@ -304,39 +354,63 @@ sub run { my ($self, $func, $cb) = @_; - my $code = 'BEGIN {' . $self->[1] . '}' - . 'syswrite STDOUT, ' . (sq $magic0) . '^' . (sq $magic1) . ';' - . $func . (aq @{ $self->[2] }) . ';' - . "\n__END__\n"; - $self->[0](sub { my $fh = shift or die "AnyEvent::Fork::Remote: create callback failed"; - - + my $code = 'BEGIN { $0 = "AnyEvent::Fork::Remote of another process"; ' . $self->[1] . "}\n" + . 'syswrite STDOUT, ' . (sq $magic0) . '^' . (sq $magic1) . ';' + . '{ sysread STDIN, my $dummy, 1 }' + . "\n$func*STDIN," . (aq @{ $self->[2] }) . ';' + . "\n__END__\n"; + + warn $code;#d# + + AnyEvent::Util::fh_nonblocking $fh, 1; + + my ($rw, $ww); + + my $ofs; + + $ww = AE::io $fh, 1, sub { + my $len = syswrite $fh, $code, 1<<20, $ofs; + + if ($len || $! == Errno::EAGAIN || $! == Errno::EWOULDBLOCK) { + $ofs += $len; + undef $ww if $ofs >= length $code; + } else { + # error + ($ww, $rw) = (); $cb->(undef); + } + }; + + my $rbuf; + + $rw = AE::io $fh, 0, sub { + my $len = sysread $fh, $rbuf, 1<<10; + + if ($len || $! == Errno::EAGAIN || $! == Errno::EWOULDBLOCK) { + $rbuf = substr $rbuf, -length $magic0 if length $rbuf > length $magic0; + + if ($rbuf eq ($magic0 ^ $magic1)) { + # all data was sent, magic was received - both + # directions should be "empty", and therefore + # the socket must accept at least a single octet, + # to signal the "child" to go on. + undef $rw; + die if $ww; # uh-oh + + syswrite $fh, "\n"; + $cb->($fh); + } + } else { + # error + ($ww, $rw) = (); $cb->(undef); + } + }; }); } -my $x = new_exec AnyEvent::Fork::Remote "/usr/bin/rsh", "rsh", "rain", "exec perl";#d# -$x->require ("Carp", "Storable");#d# -$x->send_arg (1, 2, 3);#d# -$x->eval ('sub run { die }');#d# -$x->run ("run", sub { - }); - - -=item my $proc = new_from_stdio $fh - -Creates an C object from a file handle. This file -handle must be connected to both STDIN and STDOUT of a F process. - -This form might be more convenient than C or C when -creating an C object, but the resulting object -does not support C. - -#TODO: really implement? - =back =head1 SEE ALSO