# Copyright 2008, 2009, 2010, 2011 Kevin Ryde
# This file is part of Chart.
#
# Chart is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3, or (at your option) any later version.
#
# Chart is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with Chart. If not, see .
package App::Chart::Proc::ChildPid;
use strict;
use warnings;
use Carp;
use Time::HiRes;
use POSIX qw(WNOHANG ECHILD);
# uncomment this to run the ### lines
#use Smart::Comments;
sub new {
my ($class, $pid) = @_;
return bless { pid => $pid }, $class;
}
sub DESTROY {
my ($self) = @_;
## no critic (RequireInitializationForLocalVars)
local $?; # don't let waitpid() clobber prospective exit code during exit()
### ChildPid DESTROY
$self->kill_and_wait ('TERM');
}
sub pid {
my ($self) = @_;
return $self->{'pid'};
}
# sub poll {
# my ($self) = @_;
# return $self->kill(0) == 1;
# }
sub kill_and_wait {
my ($self, $sig) = @_;
if (! $self->{'pid'}) {
$! = ECHILD;
return -1;
}
if ($self->kill ($sig) != 1) { return -1; }
Time::HiRes::usleep (10000);
my $sleeps = 0;
for (;;) {
my $status = $self->wait (WNOHANG);
if ($status != 0) { return $status; }
if ($sleeps >= 3) { last; }
sleep 1;
$sleeps++;
}
if ($self->kill ('KILL') != 1) { return -1; }
return $self->wait;
}
sub kill {
my ($self, $sig) = @_;
my $pid = $self->{'pid'} || return 0;
if (! defined $sig) { $sig = 'TERM'; }
return kill ($sig, $pid);
}
sub wait {
my ($self, $flags) = @_;
my $pid = delete $self->{'pid'}
|| do { $! = ECHILD; return -1; };
if (! defined $flags) { $flags = 0; }
### ChildPid wait: $pid
return waitpid ($pid, $flags);
}
1;
__END__
=head1 NAME
App::Chart::Proc::ChildPid -- child subprocess pid object
=for test_synopsis my ($pid)
=head1 SYNOPSIS
use App::Chart::Proc::ChildPid;
my $cp = App::Chart::Proc::ChildPid->new ($pid);
$cp->kill_and_wait;
=head1 DESCRIPTION
C keeps hold of a process ID which is a child of the
current process. If the ChildPid object is destroyed the child is killed
and waited, thus protecting against creation of zombies.
=head1 FUNCTIONS
=over 4
=item C<< App::Chart::Proc::ChildPid->new ($pid) >>
Create and return a new ChildPid holding process ID C<$pid>. There's no
check that C<$pid> is actually a child of the current process.
=item C<< $cp->kill () >>
=item C<< $cp->kill ($sig) >>
Do a C on the child process, sending it C or the given signal
C<$sig> (a signal name or number). The return is as per the core C
function, ie. 1 if successful, or 0 if no processes signalled (because the
child has been waited).
=item C<< $cp->wait() >>
Do a C on the child process and return its exit status. If the child
has already been waited the return is -1 with C<$!> set to C (no
such child).
=item C<< $cp->kill_and_wait() >>
Do a C and C combination on the child process and return its
exit status. A C is sent first, and if that doesn't kill the
process after a few seconds a C is sent (which it can't ignore).
If the child has already been waited the return is -1 with C<$!> set to
C (no such child).
=back
=head1 SEE ALSO
L
=cut