File Coverage

blib/lib/App/SuperviseMe.pm
Criterion Covered Total %
statement 31 98 31.6
branch 10 26 38.4
condition 5 12 41.6
subroutine 7 19 36.8
pod 3 3 100.0
total 56 158 35.4


line stmt bran cond sub pod time code
1             package App::SuperviseMe;
2              
3             # ABSTRACT: very simple command superviser
4             our $VERSION = '0.004'; # VERSION
5             our $AUTHORITY = 'cpan:MELO'; # AUTHORITY
6              
7 1     1   48315 use strict;
  1         4  
  1         36  
8 1     1   6 use warnings;
  1         3  
  1         31  
9 1     1   6 use Carp 'croak';
  1         2  
  1         46  
10 1     1   1733 use AnyEvent;
  1         6734  
  1         1507  
11              
12             ##############
13             # Constructors
14              
15             sub new {
16 5     5 1 42540 my ($class, %args) = @_;
17              
18 5   100     27 my $cmds = delete($args{cmds}) || [];
19 5 50       16 $cmds = [$cmds] unless ref($cmds) eq 'ARRAY';
20 5         14 for my $cmd (@$cmds) {
21 5 100       21 $cmd = [$cmd] unless ref($cmd) eq 'ARRAY';
22 5         19 $cmd = { cmd => $cmd };
23             }
24              
25 5 100       332 croak(q{Missing 'cmds',}) unless @$cmds;
26              
27 3   50     57 return bless {
      50        
28             cmds => $cmds,
29             debug => $ENV{SUPERVISE_ME_DEBUG} || $args{debug} || 0,
30             progress => $args{progress} || 0,
31             }, $class;
32             }
33              
34             sub new_from_options {
35 1     1 1 4308 my ($class) = @_;
36              
37 1         5 _out('Enter commands to supervise, one per line');
38              
39 1         2 my @cmds;
40 1         7 while (my $l = ) {
41 5         105 chomp $l;
42 5         24 $l =~ s/^\s+|\s+$//g;
43 5 100       18 next unless $l;
44 3 100       14 next if $l =~ /^#/;
45              
46 2         10 push @cmds, $l;
47             }
48              
49 1         45 return $class->new(cmds => \@cmds);
50             }
51              
52              
53             ################
54             # Start the show
55              
56             sub run {
57 0     0 1 0 my $self = shift;
58 0         0 my $sv = AE::cv;
59              
60 0     0   0 my $int_s = AE::signal 'INT' => sub { $self->_signal_all_cmds('INT', $sv); };
  0         0  
61 0     0   0 my $term_s = AE::signal 'TERM' => sub { $self->_signal_all_cmds('TERM'); $sv->send };
  0         0  
  0         0  
62              
63 0         0 for my $cmd (@{ $self->{cmds} }) {
  0         0  
64 0         0 $self->_start_cmd($cmd);
65             }
66              
67 0         0 $sv->recv;
68             }
69              
70              
71             ##########
72             # Magic...
73              
74             sub _start_cmd {
75 0     0   0 my ($self, $cmd) = @_;
76 0         0 $self->_progress("Starting '@{$cmd->{cmd}}'");
  0         0  
77              
78 0         0 my $pid = fork();
79 0 0       0 if (!defined $pid) {
80 0         0 $self->_error("fork() failed: $!");
81 0         0 $self->_restart_cmd($cmd);
82 0         0 return;
83             }
84              
85 0 0       0 if ($pid == 0) { ## Child
86 0         0 $cmd = $cmd->{cmd};
87 0         0 $self->_debug("Exec'ing '@$cmd'");
88 0         0 exec(@$cmd);
89 0         0 exit(1);
90             }
91              
92             ## parent
93 0         0 $self->_debug("Watching pid $pid for '@{$cmd->{cmd}}'");
  0         0  
94 0         0 $cmd->{pid} = $pid;
95 0     0   0 $cmd->{watcher} = AE::child $pid, sub { $self->_child_exited($cmd, @_) };
  0         0  
96              
97 0         0 return;
98             }
99              
100             sub _child_exited {
101 0     0   0 my ($self, $cmd, undef, $status) = @_;
102 0         0 $self->_debug("Child $cmd->{pid} exited, status $status: '@{$cmd->{cmd}}'");
  0         0  
103              
104 0         0 delete $cmd->{watcher};
105 0         0 delete $cmd->{pid};
106              
107 0         0 $cmd->{last_status} = $status >> 8;
108              
109 0         0 $self->_restart_cmd($cmd);
110             }
111              
112             sub _restart_cmd {
113 0     0   0 my ($self, $cmd) = @_;
114 0         0 $self->_progress("Restarting cmd '@{$cmd->{cmd}}' in 1 second");
  0         0  
115              
116 0         0 my $t;
117 0     0   0 $t = AE::timer 1, 0, sub { $self->_start_cmd($cmd); undef $t };
  0         0  
  0         0  
118             }
119              
120             sub _signal_all_cmds {
121 0     0   0 my ($self, $signal, $cv) = @_;
122 0         0 $self->_debug("Received signal $signal");
123 0         0 my $is_any_alive = 0;
124 0         0 for my $cmd (@{ $self->{cmds} }) {
  0         0  
125 0 0       0 next unless my $pid = $cmd->{pid};
126 0         0 $self->_debug("... sent signal $signal to $pid");
127 0         0 $is_any_alive++;
128 0         0 kill($signal, $pid);
129             }
130              
131 0 0 0     0 return if $cv and $is_any_alive;
132              
133 0         0 $self->_progress('Exiting...');
134 0 0       0 $cv->send if $cv;
135             }
136              
137              
138             #########
139             # Loggers
140              
141             sub _out {
142 1 50 33 1   17 return unless -t \*STDOUT && -t \*STDIN;
143              
144 0           print @_, "\n";
145             }
146              
147             sub _progress {
148 0     0     my $self = shift;
149 0 0         return unless $self->{progress};
150              
151 0           print @_, "\n";
152 0           $self->_debug('progress msg: ', @_);
153             }
154              
155             sub _debug {
156 0     0     my $self = shift;
157 0 0         return unless $self->{debug};
158              
159 0           print STDERR "DEBUG [$$] ", @_, "\n";
160             }
161              
162             sub _error {
163 0     0     shift;
164 0           print "ERROR: ", @_, "\n";
165 0           return;
166             }
167              
168             1;
169              
170             __END__