File Coverage

blib/lib/System/Command/Reaper.pm
Criterion Covered Total %
statement 74 78 94.8
branch 21 22 95.4
condition 27 37 72.9
subroutine 16 18 88.8
pod 3 3 100.0
total 141 158 89.2


line stmt bran cond sub pod time code
1             package System::Command::Reaper;
2             $System::Command::Reaper::VERSION = '1.122';
3 39     39   264 use strict;
  39         79  
  39         1178  
4 39     39   205 use warnings;
  39         77  
  39         822  
5 39     39   599 use 5.006;
  39         163  
6              
7 39     39   301 use Carp;
  39         78  
  39         2544  
8 39     39   251 use Scalar::Util qw( weaken reftype );
  39         84  
  39         2122  
9              
10 39     39   19010 use POSIX ":sys_wait_h";
  39         244928  
  39         199  
11              
12 39     39   61274 use constant MSWin32 => $^O eq 'MSWin32';
  39         90  
  39         3683  
13 39     39   251 use constant HANDLES => qw( stdin stdout stderr );
  39         73  
  39         2931  
14 39     39   246 use constant STATUS => qw( exit signal core );
  39         78  
  39         2689  
15              
16             for my $attr ( HANDLES ) {
17 39     39   240 no strict 'refs';
  39         79  
  39         3547  
18 0     0   0 *$attr = sub { return $_[0]{$attr} };
19             }
20             for my $attr ( STATUS ) {
21 39     39   279 no strict 'refs';
  39         69  
  39         34406  
22 0     0   0 *$attr = sub { $_[0]->is_terminated(); return $_[0]{$attr} };
  0         0  
23             }
24              
25             sub new {
26 99     99 1 661 my ($class, $command, $o) = @_;
27 99   50     536 $o ||= {};
28 99         1835 my $self = bless { %$o, command => $command }, $class;
29              
30             # copy/weaken the important keys
31 99         518 @{$self}{ pid => HANDLES } = @{$command}{ pid => HANDLES };
  99         661  
  99         1253  
32 99         2789 weaken $self->{$_} for ( command => HANDLES );
33              
34 99         1000 return $self;
35             }
36              
37             # this is necessary, because kill(0,pid) is misimplemented in perl core
38             my $_is_alive = MSWin32
39             ? sub { return `tasklist /FO CSV /NH /fi "PID eq $_[0]"` =~ /^"/ }
40             : sub { return kill 0, $_[0]; };
41              
42             sub is_terminated {
43 333     333 1 887 my ($self) = @_;
44 333         761 my $pid = $self->{pid};
45              
46             # Zed's dead, baby. Zed's dead.
47 333 100 100     1395 return $pid if !$_is_alive->($pid) and exists $self->{exit};
48              
49             # If that is a re-animated body, we're gonna have to kill it.
50 67         665 return $self->_reap(WNOHANG);
51             }
52              
53             sub _reap {
54 166     166   635 my ( $self, $flags ) = @_;
55              
56 166 100       747 $flags = 0 if ! defined $flags;
57              
58 166         612 my $pid = $self->{pid};
59              
60             # REPENT/THE END IS/EXTREMELY/FUCKING/NIGH
61 166 100 100     4027172 if ( !exists $self->{exit} and my $reaped = waitpid( $pid, $flags ) ) {
62              
63             # Well, it's a puzzle because, technically, you're not alive.
64 99         1052 my $zed = $reaped == $pid;
65 99 100 66     3429 carp "Child process already reaped, check for a SIGCHLD handler"
      100        
66             if !$zed && !$System::Command::QUIET && !MSWin32;
67              
68             # What do you think? "Zombie Kill of the Week"?
69 99 100       9656 @{$self}{ STATUS() }
  99         1551  
70             = $zed
71             ? ( $? >> 8, $? & 127, $? & 128 )
72             : ( -1, -1, -1 );
73              
74             # Who died and made you fucking king of the zombies?
75 99 100       612 if ( defined( my $cmd = $self->{command} ) ) {
76 69         476 @{$cmd}{ STATUS() } = @{$self}{ STATUS() };
  69         437  
  69         236  
77              
78             # I know you're here, because I can smell your brains.
79 69         232 my $o = $cmd->{options};
80             defined reftype( $o->{$_} )
81             and reftype( $o->{$_} ) eq 'SCALAR'
82 21         660 and ${ $o->{$_} } = $self->{$_}
83 69   66     2050 for STATUS();
      66        
84             }
85              
86             # I think it's safe to assume it isn't a zombie.
87 0         0 print { $self->{th} } "System::Command xit[$pid]: ",
88             join( ', ', map "$_: $self->{$_}", STATUS() ), "\n"
89 99 50       411 if $self->{trace};
90              
91 99         533 return $reaped; # It's dead, Jim!
92             }
93              
94             # Look! It's moving. It's alive. It's alive...
95 67         456 return;
96             }
97              
98             sub close {
99 99     99 1 409 my ($self) = @_;
100              
101             # close all pipes
102 99         322 my ( $in, $out, $err ) = @{$self}{qw( stdin stdout stderr )};
  99         396  
103 99 100 33     1949 $in and $in->opened and $in->close || carp "error closing stdin: $!";
      100        
104 99 100 33     4025 $out and $out->opened and $out->close || carp "error closing stdout: $!";
      100        
105 99 100 33     2633 $err and $err->opened and $err->close || carp "error closing stderr: $!";
      100        
106              
107             # and wait for the child (if any)
108 99         2897 $self->_reap();
109              
110 99         1666 return $self;
111             }
112              
113             sub DESTROY {
114 97     97   193665 my ($self) = @_;
115 97         892 local $?;
116 97         628 local $!;
117 97 100       1251 $self->close if !exists $self->{exit};
118             }
119              
120             1;
121              
122             __END__