Acme-Sort-Sleep
view release on metacpan or search on metacpan
local/lib/perl5/IO/Async/ChildManager.pm view on Meta::CPAN
where an external program is being started in order to interact with it via
file IO, or even C<run_child> when only the final result is required, rather
than interaction while it is running.
=cut
sub spawn_child
{
my $self = shift;
my %params = @_;
my $command = delete $params{command};
my $code = delete $params{code};
my $setup = delete $params{setup};
my $on_exit = delete $params{on_exit};
if( %params ) {
croak "Unrecognised options to spawn: " . join( ",", keys %params );
}
defined $command and defined $code and
croak "Cannot pass both 'command' and 'code' to spawn";
defined $command or defined $code or
croak "Must pass one of 'command' or 'code' to spawn";
my @setup = defined $setup ? $self->_check_setup_and_canonicise( $setup ) : ();
my $loop = $self->{loop};
my ( $readpipe, $writepipe );
{
# Ensure it's FD_CLOEXEC - this is a bit more portable than manually
# fiddling with F_GETFL and F_SETFL (e.g. MSWin32)
local $^F = -1;
( $readpipe, $writepipe ) = IO::Async::OS->pipepair or croak "Cannot pipe() - $!";
}
if( defined $command ) {
my @command = ref( $command ) ? @$command : ( $command );
$code = sub {
no warnings;
exec( @command );
return;
};
}
my $kid = $loop->fork(
code => sub {
# Child
close( $readpipe );
$self->_spawn_in_child( $writepipe, $code, \@setup );
},
);
# Parent
close( $writepipe );
return $self->_spawn_in_parent( $readpipe, $kid, $on_exit );
}
=head2 C<setup> array
This array gives a list of file descriptor operations to perform in the child
process after it has been C<fork(2)>ed from the parent, before running the code
or command. It consists of name/value pairs which are ordered; the operations
are performed in the order given.
=over 8
=item fdI<n> => ARRAY
Gives an operation on file descriptor I<n>. The first element of the array
defines the operation to be performed:
=over 4
=item [ 'close' ]
The file descriptor will be closed.
=item [ 'dup', $io ]
The file descriptor will be C<dup2(2)>ed from the given IO handle.
=item [ 'open', $mode, $file ]
The file descriptor will be opened from the named file in the given mode. The
C<$mode> string should be in the form usually given to the C<open> function;
such as '<' or '>>'.
=item [ 'keep' ]
The file descriptor will not be closed; it will be left as-is.
=back
A non-reference value may be passed as a shortcut, where it would contain the
name of the operation with no arguments (i.e. for the C<close> and C<keep>
operations).
=item IO => ARRAY
Shortcut for passing C<fdI<n>>, where I<n> is the fileno of the IO
reference. In this case, the key must be a reference that implements the
C<fileno> method. This is mostly useful for
$handle => 'keep'
=item fdI<n> => IO
A shortcut for the C<dup> case given above.
=item stdin => ...
=item stdout => ...
=item stderr => ...
Shortcuts for C<fd0>, C<fd1> and C<fd2> respectively.
=item env => HASH
A reference to a hash to set as the child process's environment.
local/lib/perl5/IO/Async/ChildManager.pm view on Meta::CPAN
$key eq "stderr" and $key = "fd2";
# Rewrite other filehandles
ref $key and eval { $key->fileno; 1 } and $key = "fd" . $key->fileno;
if( $key =~ m/^fd(\d+)$/ ) {
my $fd = $1;
my $ref = ref $value;
if( !$ref ) {
$value = [ $value ];
}
elsif( $ref eq "ARRAY" ) {
# Already OK
}
elsif( $ref eq "GLOB" or eval { $value->isa( "IO::Handle" ) } ) {
$value = [ 'dup', $value ];
}
else {
croak "Unrecognised reference type '$ref' for file descriptor $fd";
}
my $operation = $value->[0];
grep { $_ eq $operation } qw( open close dup keep ) or
croak "Unrecognised operation '$operation' for file descriptor $fd";
}
elsif( $key eq "env" ) {
ref $value eq "HASH" or croak "Expected HASH reference for 'env' setup key";
}
elsif( $key eq "nice" ) {
$value =~ m/^\d+$/ or croak "Expected integer for 'nice' setup key";
}
elsif( $key eq "chdir" ) {
# This isn't a purely watertight test, but it does guard against
# silly things like passing a reference - directories such as
# ARRAY(0x12345) are unlikely to exist
-d $value or croak "Working directory '$value' does not exist";
}
elsif( $key eq "setuid" ) {
$value =~ m/^\d+$/ or croak "Expected integer for 'setuid' setup key";
}
elsif( $key eq "setgid" ) {
$value =~ m/^\d+$/ or croak "Expected integer for 'setgid' setup key";
$has_setgroups and carp "It is suggested to 'setgid' before 'setgroups'";
}
elsif( $key eq "setgroups" ) {
ref $value eq "ARRAY" or croak "Expected ARRAY reference for 'setgroups' setup key";
m/^\d+$/ or croak "Expected integer in 'setgroups' array" for @$value;
$has_setgroups = 1;
}
else {
croak "Unrecognised setup operation '$key'";
}
push @setup, $key => $value;
}
return @setup;
}
sub _spawn_in_parent
{
my $self = shift;
my ( $readpipe, $kid, $on_exit ) = @_;
my $loop = $self->{loop};
# We need to wait for both the errno pipe to close, and for waitpid
# to give us an exit code. We'll form two closures over these two
# variables so we can cope with those happening in either order
my $dollarbang;
my ( $dollarat, $length_dollarat );
my $exitcode;
my $pipeclosed = 0;
$loop->add( IO::Async::Stream->new(
notifier_name => "statuspipe,kid=$kid",
read_handle => $readpipe,
on_read => sub {
my ( $self, $buffref, $eof ) = @_;
if( !defined $dollarbang ) {
if( length( $$buffref ) >= 2 * LENGTH_OF_I ) {
( $dollarbang, $length_dollarat ) = unpack( "II", $$buffref );
substr( $$buffref, 0, 2 * LENGTH_OF_I, "" );
return 1;
}
}
elsif( !defined $dollarat ) {
if( length( $$buffref ) >= $length_dollarat ) {
$dollarat = substr( $$buffref, 0, $length_dollarat, "" );
return 1;
}
}
if( $eof ) {
$dollarbang = 0 if !defined $dollarbang;
if( !defined $length_dollarat ) {
$length_dollarat = 0;
$dollarat = "";
}
$pipeclosed = 1;
if( defined $exitcode ) {
local $! = $dollarbang;
$on_exit->( $kid, $exitcode, $!, $dollarat );
}
}
return 0;
}
) );
$loop->watch_child( $kid => sub {
( my $kid, $exitcode ) = @_;
if( $pipeclosed ) {
( run in 1.974 second using v1.01-cache-2.11-cpan-2398b32b56e )