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.121';
3 39     39   264 use strict;
  39         82  
  39         1119  
4 39     39   199 use warnings;
  39         70  
  39         876  
5 39     39   806 use 5.006;
  39         140  
6              
7 39     39   241 use Carp;
  39         78  
  39         2117  
8 39     39   241 use Scalar::Util qw( weaken reftype );
  39         157  
  39         2655  
9              
10 39     39   21244 use POSIX ":sys_wait_h";
  39         258038  
  39         213  
11              
12 39     39   62088 use constant MSWin32 => $^O eq 'MSWin32';
  39         94  
  39         4244  
13 39     39   266 use constant HANDLES => qw( stdin stdout stderr );
  39         79  
  39         2645  
14 39     39   230 use constant STATUS => qw( exit signal core );
  39         129  
  39         2830  
15              
16             for my $attr ( HANDLES ) {
17 39     39   267 no strict 'refs';
  39         82  
  39         3851  
18 0     0   0 *$attr = sub { return $_[0]{$attr} };
19             }
20             for my $attr ( STATUS ) {
21 39     39   262 no strict 'refs';
  39         97  
  39         35013  
22 0     0   0 *$attr = sub { $_[0]->is_terminated(); return $_[0]{$attr} };
  0         0  
23             }
24              
25             sub new {
26 99     99 1 1116 my ($class, $command, $o) = @_;
27 99   50     886 $o ||= {};
28 99         2761 my $self = bless { %$o, command => $command }, $class;
29              
30             # copy/weaken the important keys
31 99         1037 @{$self}{ pid => HANDLES } = @{$command}{ pid => HANDLES };
  99         1050  
  99         1791  
32 99         3158 weaken $self->{$_} for ( command => HANDLES );
33              
34 99         1531 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 1096 my ($self) = @_;
44 333         884 my $pid = $self->{pid};
45              
46             # Zed's dead, baby. Zed's dead.
47 333 100 100     1839 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         847 return $self->_reap(WNOHANG);
51             }
52              
53             sub _reap {
54 166     166   770 my ( $self, $flags ) = @_;
55              
56 166 100       679 $flags = 0 if ! defined $flags;
57              
58 166         509 my $pid = $self->{pid};
59              
60             # REPENT/THE END IS/EXTREMELY/FUCKING/NIGH
61 166 100 100     4031486 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         968 my $zed = $reaped == $pid;
65 99 100 66     3905 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       8734 @{$self}{ STATUS() }
  99         2102  
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       1156 if ( defined( my $cmd = $self->{command} ) ) {
76 69         238 @{$cmd}{ STATUS() } = @{$self}{ STATUS() };
  69         439  
  69         260  
77              
78             # I know you're here, because I can smell your brains.
79 69         256 my $o = $cmd->{options};
80             defined reftype( $o->{$_} )
81             and reftype( $o->{$_} ) eq 'SCALAR'
82 21         722 and ${ $o->{$_} } = $self->{$_}
83 69   66     2342 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       571 if $self->{trace};
90              
91 99         593 return $reaped; # It's dead, Jim!
92             }
93              
94             # Look! It's moving. It's alive. It's alive...
95 67         557 return;
96             }
97              
98             sub close {
99 99     99 1 488 my ($self) = @_;
100              
101             # close all pipes
102 99         347 my ( $in, $out, $err ) = @{$self}{qw( stdin stdout stderr )};
  99         473  
103 99 100 33     1846 $in and $in->opened and $in->close || carp "error closing stdin: $!";
      100        
104 99 100 33     4992 $out and $out->opened and $out->close || carp "error closing stdout: $!";
      100        
105 99 100 33     3196 $err and $err->opened and $err->close || carp "error closing stderr: $!";
      100        
106              
107             # and wait for the child (if any)
108 99         2625 $self->_reap();
109              
110 99         1888 return $self;
111             }
112              
113             sub DESTROY {
114 97     97   212622 my ($self) = @_;
115 97         533 local $?;
116 97         656 local $!;
117 97 100       1716 $self->close if !exists $self->{exit};
118             }
119              
120             1;
121              
122             __END__