Acme-Sort-Sleep

 view release on metacpan or  search on metacpan

local/lib/perl5/IO/Async/Process.pm  view on Meta::CPAN

=cut

sub _init
{
   my $self = shift;
   $self->SUPER::_init( @_ );

   $self->{to_close}   = {};
   $self->{finish_futures} = [];
}

=head1 PARAMETERS

The following named parameters may be passed to C<new> or C<configure>:

=head2 on_finish => CODE

=head2 on_exception => CODE

CODE reference for the event handlers.

Once the C<on_finish> continuation has been invoked, the C<IO::Async::Process>
object is removed from the containing L<IO::Async::Loop> object.

The following parameters may be passed to C<new>, or to C<configure> before
the process has been started (i.e. before it has been added to the C<Loop>).
Once the process is running these cannot be changed.

=head2 command => ARRAY or STRING

Either a reference to an array containing the command and its arguments, or a
plain string containing the command. This value is passed into perl's
C<exec(2)> function.

=head2 code => CODE

A block of code to execute in the child process. It will be called in scalar
context inside an C<eval> block.

=head2 setup => ARRAY

Optional reference to an array to pass to the underlying C<Loop>
C<spawn_child> method.

=head2 fdI<n> => HASH

A hash describing how to set up file descriptor I<n>. The hash may contain the
following keys:

=over 4

=item via => STRING

Configures how this file descriptor will be configured for the child process.
Must be given one of the following mode names:

=over 4

=item pipe_read

The child will be given the writing end of a C<pipe(2)>; the parent may read
from the other.

=item pipe_write

The child will be given the reading end of a C<pipe(2)>; the parent may write
to the other. Since an EOF condition of this kind of handle cannot reliably be
detected, C<on_finish> will not wait for this type of pipe to be closed.

=item pipe_rdwr

Only valid on the C<stdio> filehandle. The child will be given the reading end
of one C<pipe(2)> on STDIN and the writing end of another on STDOUT. A single
Stream object will be created in the parent configured for both filehandles.

=item socketpair

The child will be given one end of a C<socketpair(2)>; the parent will be
given the other. The family of this socket may be given by the extra key
called C<family>; defaulting to C<unix>. The socktype of this socket may be
given by the extra key called C<socktype>; defaulting to C<stream>. If the
type is not C<SOCK_STREAM> then a L<IO::Async::Socket> object will be
constructed for the parent side of the handle, rather than
L<IO::Async::Stream>.

=back

Once the filehandle is set up, the C<fd> method (or its shortcuts of C<stdin>,
C<stdout> or C<stderr>) may be used to access the
L<IO::Async::Handle>-subclassed object wrapped around it.

The value of this argument is implied by any of the following alternatives.

=item on_read => CODE

The child will be given the writing end of a pipe. The reading end will be
wrapped by an L<IO::Async::Stream> using this C<on_read> callback function.

=item into => SCALAR

The child will be given the writing end of a pipe. The referenced scalar will
be filled by data read from the child process. This data may not be available
until the pipe has been closed by the child.

=item from => STRING

The child will be given the reading end of a pipe. The string given by the
C<from> parameter will be written to the child. When all of the data has been
written the pipe will be closed.

=back

=head2 stdin => ...

=head2 stdout => ...

=head2 stderr => ...

Shortcuts for C<fd0>, C<fd1> and C<fd2> respectively.

=head2 stdio => ...

Special filehandle to affect STDIN and STDOUT at the same time. This
filehandle supports being configured for both reading and writing at the same
time.

=cut

sub configure
{
   my $self = shift;
   my %params = @_;

   foreach (qw( on_finish on_exception )) {
      $self->{$_} = delete $params{$_} if exists $params{$_};
   }

   # All these parameters can only be configured while the process isn't
   # running
   my %setup_params;
   foreach (qw( code command setup stdin stdout stderr stdio ), grep { m/^fd\d+$/ } keys %params ) {
      $setup_params{$_} = delete $params{$_} if exists $params{$_};
   }

   if( $self->is_running ) {
      keys %setup_params and croak "Cannot configure a running Process with " . join ", ", keys %setup_params;
   }

   defined( exists $setup_params{code} ? $setup_params{code} : $self->{code} ) +
      defined( exists $setup_params{command} ? $setup_params{command} : $self->{command} ) <= 1 or
      croak "Cannot have both 'code' and 'command'";

   foreach (qw( code command setup )) {
      $self->{$_} = delete $setup_params{$_} if exists $setup_params{$_};
   }

   $self->configure_fd( 0, %{ delete $setup_params{stdin}  } ) if $setup_params{stdin};
   $self->configure_fd( 1, %{ delete $setup_params{stdout} } ) if $setup_params{stdout};
   $self->configure_fd( 2, %{ delete $setup_params{stderr} } ) if $setup_params{stderr};

   $self->configure_fd( 'io', %{ delete $setup_params{stdio} } ) if $setup_params{stdio};

   # All the rest are fd\d+
   foreach ( keys %setup_params ) {
      my ( $fd ) = m/^fd(\d+)$/ or croak "Expected 'fd\\d+'";
      $self->configure_fd( $fd, %{ $setup_params{$_} } );
   }

   $self->SUPER::configure( %params );
}

# These are from the perspective of the parent
use constant FD_VIA_PIPEREAD  => 1;
use constant FD_VIA_PIPEWRITE => 2;
use constant FD_VIA_PIPERDWR  => 3; # Only valid for stdio pseudo-fd
use constant FD_VIA_SOCKETPAIR => 4;

my %via_names = (
   pipe_read  => FD_VIA_PIPEREAD,
   pipe_write => FD_VIA_PIPEWRITE,
   pipe_rdwr  => FD_VIA_PIPERDWR,
   socketpair => FD_VIA_SOCKETPAIR,
);

sub configure_fd
{
   my $self = shift;
   my ( $fd, %args ) = @_;

   $self->is_running and croak "Cannot configure fd $fd in a running Process";

   if( $fd eq "io" ) {
      exists $self->{fd_opts}{$_} and croak "Cannot configure stdio since fd$_ is already defined" for 0 .. 1;
   }
   elsif( $fd == 0 or $fd == 1 ) {
      exists $self->{fd_opts}{io} and croak "Cannot configure fd$fd since stdio is already defined";
   }

   my $opts = $self->{fd_opts}{$fd} ||= {};
   my $via = $opts->{via};

   my ( $wants_read, $wants_write );

   if( my $via_name = delete $args{via} ) {
      defined $via and
         croak "Cannot change the 'via' mode of fd$fd now that it is already configured";

      $via = $via_names{$via_name} or
         croak "Unrecognised 'via' name of '$via_name'";
   }

   if( my $on_read = delete $args{on_read} ) {
      $opts->{handle}{on_read} = $on_read;

      $wants_read++;
   }
   elsif( my $into = delete $args{into} ) {
      $opts->{handle}{on_read} = sub {
         my ( undef, $buffref, $eof ) = @_;
         $$into .= $$buffref if $eof;
         return 0;
      };

      $wants_read++;
   }

   if( defined( my $from = delete $args{from} ) ) {
      $opts->{from} = $from;

      $wants_write++;
   }

local/lib/perl5/IO/Async/Process.pm  view on Meta::CPAN

{
   my $self = shift;
   my ( $loop ) = @_;

   $self->{code} or $self->{command} or
      croak "Require either 'code' or 'command' in $self";

   $self->can_event( "on_finish" ) or
      croak "Expected either an on_finish callback or to be able to ->on_finish";

   my @setup;
   push @setup, @{ $self->{setup} } if $self->{setup};

   push @setup, $self->_prepare_fds( $loop );

   my $finish_futures = delete $self->{finish_futures};

   my ( $exitcode, $dollarbang, $dollarat );
   push @$finish_futures, my $exit_future = $loop->new_future;

   $self->{pid} = $loop->spawn_child(
      code    => $self->{code},
      command => $self->{command},

      setup => \@setup,

      on_exit => $self->_capture_weakself( sub {
         ( my $self, undef, $exitcode, $dollarbang, $dollarat ) = @_;

         $self->debug_printf( "EXIT status=0x%04x", $exitcode ) if $self;
         $exit_future->done unless $exit_future->is_cancelled;
      } ),
   );
   $self->{running} = 1;

   $self->SUPER::_add_to_loop( @_ );

   $_->close for values %{ delete $self->{to_close} };

   my $is_code = defined $self->{code};

   $self->{finish_future} = Future->needs_all( @$finish_futures )
      ->on_done( $self->_capture_weakself( sub {
         my $self = shift or return;

         $self->{exitcode} = $exitcode;
         $self->{dollarbang} = $dollarbang;
         $self->{dollarat}   = $dollarat;

         undef $self->{running};

         if( $is_code ? $dollarat eq "" : $dollarbang == 0 ) {
            $self->invoke_event( on_finish => $exitcode );
         }
         else {
            $self->maybe_invoke_event( on_exception => $dollarat, $dollarbang, $exitcode ) or
               # Don't have a way to report dollarbang/dollarat
               $self->invoke_event( on_finish => $exitcode );
         }

         $self->remove_from_parent;
      } ),
   );
}

sub DESTROY
{
   my $self = shift;
   $self->{finish_future}->cancel if $self->{finish_future};
}

sub notifier_name
{
   my $self = shift;
   if( length( my $name = $self->SUPER::notifier_name ) ) {
      return $name;
   }

   return "nopid" unless my $pid = $self->pid;
   return "[$pid]" unless $self->is_running;
   return "$pid";
}

=head1 METHODS

=cut

=head2 pid

   $pid = $process->pid

Returns the process ID of the process, if it has been started, or C<undef> if
not. Its value is preserved after the process exits, so it may be inspected
during the C<on_finish> or C<on_exception> events.

=cut

sub pid
{
   my $self = shift;
   return $self->{pid};
}

=head2 kill

   $process->kill( $signal )

Sends a signal to the process

=cut

sub kill
{
   my $self = shift;
   my ( $signal ) = @_;

   kill $signal, $self->pid or croak "Cannot kill() - $!";
}

=head2 is_running



( run in 0.566 second using v1.01-cache-2.11-cpan-39bf76dae61 )