File Coverage

blib/lib/Proc/PersistentControl.pm
Criterion Covered Total %
statement 266 428 62.1
branch 77 180 42.7
condition 27 58 46.5
subroutine 27 36 75.0
pod 4 5 80.0
total 401 707 56.7


line stmt bran cond sub pod time code
1             package Proc::PersistentControl; # -*-perl-*-
2             #
3             # Author: Michael Staats 2014
4             #
5             # $Id: PersistentControl.pm 713 2015-03-13 12:05:17Z michael $
6             #
7              
8             =head1 NAME
9              
10             Proc::PersistentControl - Start and Control Background Processes ("jobs", process groups)
11            
12             =head1 SYNOPSIS
13              
14             use Proc::PersistentControl;
15              
16             my $controller = Proc::PersistentControl->new();
17              
18             my $Proc =
19             $controller->StartProc({ [ timeout => 42, psynctime => 5,
20             Key => 'Value', ... ] },
21             "runme args");
22              
23             my @Procs = $controller->ProcList(['Key', 'Value']);
24              
25             my @Procs = $controller->RipeList();
26              
27             my $alive = $Proc->IsAlive();
28              
29             my $ripe = $Proc->IsRipe();
30              
31             my $info = $Proc->Info();
32              
33             my $info = $Proc->Reap();
34              
35             $Proc->Kill();
36              
37             =head1 DESCRIPTION
38              
39             WARNING: This module (and its pod) is beta.
40              
41             The method that is used by this module is calling for race conditions all
42             over the place... Since version 1.0 most of these should be found. But there
43             will still be bugs.
44              
45             This module creates background processes and allows to track
46             them from multiple invocations of a perl program, i. e. not only
47             from the parent.
48              
49             This is done in quite a KISS way: Information about background
50             processes is stored in one directory per started process.
51              
52             Special care is taken so that killing processes will also kill
53             all child processes (i. e. grandchildren).
54              
55             A timeout for the processes can be specified.
56              
57             This module works on Unix and Windows. On Windows, Win32, Win32::Process
58             and Win32::Job is required.
59              
60             This module is intended to be as simple as possible. It should have as
61             few other modules as prerequisites as possible, only modules
62             that are likely to be installed in a "typical" perl installation (i. e.
63             core modules, or "standard" Win32 modules on Windows). It should be
64             usable on any Unix with the perl that comes with the OS.
65              
66             The intended typical use cases of this module are things like programs
67             that need to start and control "a few" background processes (something
68             like max. 10 per minute or so), but consistently over multiple invocations
69             of the program (like when they are scheduled by cron).
70             Probably not a busy web server that needs to start hundreds of CGI
71             processes per second.
72            
73             =head1 The Controller Object, Process objects, and their Methods
74              
75             =head2 Methods for the controller object
76            
77             =over 4
78              
79             =item Proc::PersistentControl->new([directory => $dir]);
80              
81             Creates a controller object using $dir as place to store the persistent
82             process information. If 'directory' is not specified, a directory
83             called "ProcPersCtrl" will be created in "/var/tmp/$>" (Unix with /var/tmp),
84             or in File::Spec->tmpdir() . "/$>" (Unix without /var/tmp), or in
85             File::Spec->tmpdir() (Windows). (Note that tmpdir() is not tempdir(),
86             i. e. the directory will always be the same. For certain values
87             of 'always'.)
88              
89             Note that preferring /var/tmp over tmpdir() allows information to survive
90             a reboot on systems where /tmp is a tmpfs or similar. (This does not mean that
91             your jobs will survive a reboot (they won't), and also the controller
92             information might be corrupt if a reboot (or crash, or kill -9) kills your
93             processes the hard way).
94            
95             =item $controller->StartProc({ [ timeout => 42, BLA => 'bla', ... ] },
96             "runme args");
97              
98             =item $controller->StartProc({ [ timeout => 42, BLA => 'bla', ... ] },
99             "runme", "arg1", "arg2", ...);
100              
101             Start the program "runme" with command line arguments, optionally specifying
102             a timeout after which the program will be killed. Other key-value pairs in
103             the options are user defined only and can be retrieved via the Info() and
104             Reap() methods, and be used to find processes by key-value pairs with the
105             ProcList() method. Keys must not start with underscore, these are used
106             internally (but will also be returned by Info() etc).
107              
108             The program can be a "binary/excutable" that is in the $PATH (%PATH%),
109             or a "script".
110             Just try. Unix magic "#!" will also work under Windows (and even more...).
111              
112             This method returns an object of class Proc::PersistentControl::Proc which
113             has the methods described further below.
114              
115             Since the internal information of the controller is stored in the filesystem,
116             you can just terminate your program that uses the controller, start a new one
117             later (giving the same directory to new()) and use all the methods described
118             below. (But see Reap(), which destroys information).
119              
120             =item $controller->ProcList(['Key', 'Value'])
121              
122             Returns a list of Proc::PersistentControl::Proc objects that are
123             under control of the controller object.
124             If a Key-Value pair is given, only processes with this Key-Value
125             pair in their options are returned (see StartProc()).
126              
127             =item $controller->RipeList()
128            
129             Returns a list of Proc::PersistentControl::Proc objects that are
130             under control of the controller object and have terminated, i. e.
131             are ready for reaping.
132              
133             =back
134            
135             =head2 Methods for process objects
136              
137             =over 4
138            
139             =item $Proc->IsAlive()
140            
141             Returns true if $Proc is still running.
142              
143             =item $Proc->IsRipe()
144              
145             equivalent to "not $Proc->IsAlive()"
146            
147             =item $Proc->Info()
148              
149             Returns a reference to hash that contains information about a process.
150              
151             Usage:
152              
153             sub type {
154             print "$_[0]:\n";
155             open(T, $_[0]);
156             print while ();
157             close(T);
158             }
159              
160             sub printInfo {
161             my $r = shift;
162             foreach my $k (keys(%$r)) {
163             my $v = $r->{$k};
164             print "$k=$v\n";
165             }
166             type($r->{_dir} . '/STDOUT');
167             type($r->{_dir} . '/STDERR');
168             }
169              
170             printInfo($Proc->Info());
171              
172             =item $Proc->Reap()
173              
174             Returns the process object's "Info()" information if $Proc->IsRipe().
175            
176             The reaped information will be DESTROY'd after the process
177             object goes out of scope. So make sure you use/copy the information
178             before that.
179             (Reap it and eat it before it gets bad).
180              
181             =item $Proc->Kill()
182              
183             Kills the operating system process(es) that belong to $Proc.
184             Should also kill grandchildren.
185              
186             =back
187              
188             =head1 BUGS
189              
190             The "make test" tests could be more detailed (but check out the
191             examples, too.)
192            
193             If you use controller objects with the same directory in parallel, be
194             aware that Reap() will reap anything it can. If two calls to Reap() for
195             the same process intersect, the result is unpredictable. So just don't
196             do that, call Reap() only in one of the programs. Other behaviour when
197             using one directory with more than one controller at the same time
198             is considered to be a feature.
199              
200             Using this module might interfere with your code if it also installs
201             signal handlers, wait()s, etc. So don't do that.
202              
203             The method to store information about the processes should use a more
204             structured data format (like Persistent::File or so, but no more pre-reqs
205             should be added).
206            
207             =head1 Examples
208              
209             Examples should be available in the Proc/PersistentControl/example
210             directory (depending on your installation).
211              
212             =head1 AUTHOR
213              
214             Michael Staats, Emichael.staats@gmx.euE
215              
216             =head1 COPYRIGHT AND LICENSE
217              
218             Copyright (C) 2014 by Michael Staats
219              
220             This library is free software; you can redistribute it and/or modify
221             it under the same terms as Perl itself, either Perl version 5.14.2 or,
222             at your option, any later version of Perl 5 you may have available.
223            
224             =cut
225              
226             require Exporter;
227 7     7   29589 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  7         14  
  7         525  
228              
229             @ISA = qw(Exporter);
230              
231             $VERSION = '1.2';
232              
233 7     7   28 use strict;
  7         7  
  7         203  
234 7     7   28 use File::Path qw(mkpath rmtree); # legacy interface, works with older perls
  7         21  
  7         378  
235 7     7   28 use File::Spec;
  7         7  
  7         126  
236 7     7   4655 use File::Temp 'tempdir'; # also old behaviour, we need the dir permanently
  7         122857  
  7         399  
237 7     7   3794 use POSIX ":sys_wait_h";
  7         34132  
  7         28  
238 7     7   6629 use Carp;
  7         7  
  7         637  
239              
240             BEGIN {
241 7 50   7   22015 if ($^O =~ /^MSWin/) {
242 0         0 require Win32; Win32->import();
  0         0  
243 0         0 require Win32::Process; Win32::Process->import();
  0         0  
244             }
245             }
246              
247             ######################################################################
248             # the new() method for the controller object
249             #
250             my $_debugfh = undef; # quick and dirty global var...
251              
252             sub new {
253 7     7 1 1351 my $class = shift;
254 7         28 my %opthash = @_;
255              
256 7   50     63 $opthash{nannyname} ||= 'ppc-nanny';
257 7   50     49 $opthash{closefdmax} ||= 12;
258            
259 7         28 my $self = { _opts => \%opthash };
260 7         28 $self->{sdir} = $opthash{directory};
261 7 50       35 if (not $self->{sdir}) {
262 7 50       56 if ($^O !~ /^MSWin/) {
263 7 50       217 $self->{sdir} =
264             ((-d '/var/tmp') ? '/var/tmp' : File::Spec->tmpdir()) .
265             "/$>/ProcPersCtrl";
266             } else {
267 0         0 $self->{sdir} =
268             Win32::GetLongPathName(File::Spec->tmpdir()) . "\\ProcPersCtrl";
269             #File::Spec->tmpdir() . "\\ProcPersCtrl";
270             }
271             }
272              
273 7         28 my $sdir = $self->{sdir};
274 7 50       35 eval { mkpath($sdir) unless (-d $sdir); };
  7         140  
275 7 50       98 croak "'$sdir' is not a directory and could not mkpath() it: $!\n"
276             if (not -d $sdir);
277              
278 7 50       35 if ($opthash{debug}) {
279 0         0 my $file = $sdir . '/debug.out';
280 0         0 my $size = (stat($file))[7];
281 0 0 0     0 if ($size and $size > 102400) {
282 0         0 rename($file, $file . '.old');
283             }
284 0 0       0 open($_debugfh, '>>', $file) ||
285             carp "Cannot append to $file: $!";
286 0         0 select((select($_debugfh), $|=1)[0]);
287 0         0 eval { require Time::HiRes; };
  0         0  
288 0 0       0 if ($@) {
289 0         0 print $_debugfh "No Time::HiRes ($@), sorry...\n";
290             }
291             }
292            
293 7         28 my $rdir = $sdir . '/reaped';
294 7 50 33     245 if (-e $rdir and not -d $rdir) {
295 0         0 carp "'$rdir' exists and is not a directory...\n";
296 0         0 unlink($rdir);
297             }
298 7 50       112 if (not -d $rdir) {
299 0 0       0 mkpath($rdir) or croak "Could not mkpath('$rdir'): $!\n";
300             }
301            
302 7         28 bless $self, $class;
303 7         35 return $self;
304             }
305              
306             sub debug {
307 132 50   132 0 463 return undef unless $_debugfh;
308 0         0 my $clf = (caller(1))[3];
309 0   0     0 $clf ||= (caller())[1];
310              
311 0         0 my $msg = join(' ', @_);
312 0 0       0 $msg .= "\n" unless ($msg =~ /\n$/);
313 0 0       0 my $ts = $INC{'Time/HiRes.pm'} ? Time::HiRes::time() : time();
314 0         0 print $_debugfh $ts, ' ', $$, ' ', $clf, ': ', $msg;
315 0         0 return 1;
316             }
317              
318             ######################################################################
319             # system dependent stuff
320              
321             sub _pid_alive($) {
322 3     3   7 my $pid = shift;
323              
324 3         20 debug($pid);
325              
326 3 50 33     37 return 0 if (not $pid or $pid <= 0);
327            
328 3 50       19 if ($^O !~ /^MSWin/) {
329 3         37 my $ret = kill(0, $pid);
330 3         10 debug("returning $ret");
331 3         20 return $ret
332             }
333            
334             # Windows
335 0         0 my $process;
336 0 0       0 if (Win32::Process::Open($process, $pid, 0)) {
337 0         0 debug("Win32::Process::Open ok");
338 0         0 my $RC;
339 0         0 $process->GetExitCode($RC);
340 0         0 debug("Win32 GetExitCode RC=$RC");
341 0 0       0 if ($RC == Win32::Process::STILL_ACTIVE()) {
342 0         0 debug("returning 1");
343 0         0 return 1;
344             }
345             }
346 0         0 debug("returning 0");
347 0         0 return 0;
348             }
349              
350             # Using these two variables looks like a bug: it seems they
351             # are re-used for new children. But they aren't, the are
352             # only used in the fork()ed child, and this forks only one
353             # grandchild.
354              
355             my $_unix_grandch_pid;
356             my $_unix_grandch_dir;
357              
358             sub _unix_intr_sighandler {
359 0     0   0 my $sig = shift;
360 0         0 $SIG{INT} = 'IGNORE';
361 0         0 $SIG{QUIT} = 'IGNORE';
362 0         0 $SIG{TERM} = 'IGNORE';
363              
364 0 0       0 if ($_unix_grandch_pid) {
365 0         0 carp "$0:ProcPersCtrl:$$: Caught SIG$sig, killing '$_unix_grandch_pid'...\n";
366 0         0 _unix_kill($_unix_grandch_pid); # calls the CHLD handler...
367             }
368 0         0 exit(0);
369             }
370              
371             sub _unix_chld_sighandler {
372 3     3   15 my $child;
373 3         135 while (($child = waitpid(-1, WNOHANG)) > 0) {
374 3         53 my $RC = $? >> 8;
375 3         52 debug("Child $child died with RC=$RC (my grandchild $_unix_grandch_pid)");
376 3 50       17 if ($child == $_unix_grandch_pid) {
377             # this is what we have been waiting for
378 3         12 $_unix_grandch_pid = undef;
379 3         20 debug("Writing rc and endtime to $_unix_grandch_dir");
380 3 50       388 open(RC, '>', $_unix_grandch_dir . "/RC=$RC") or
381             carp "Could not write $_unix_grandch_dir/RC=$RC: $!";
382 3         39 close(RC);
383 3 50       109 open(I, '>>', $_unix_grandch_dir . "/info") or
384             carp "Could not append to $_unix_grandch_dir/info: $!";
385 3         71 print I "_endtime=", time(), "\n";
386 3         91 close(I);
387             }
388             }
389 3         400649 $SIG{CHLD} = \&_unix_chld_sighandler; # ... sysV
390             }
391              
392             sub _unix_spawn {
393 15     15   27 my $pdir = shift;
394 15         18 my $opt = shift;
395 15         30 my @cmd = @_;
396              
397 15         87 debug("called with $pdir for $cmd[0] ...");
398            
399 15         219 $SIG{CHLD} = 'IGNORE'; # for now
400            
401 15         10583 my $childpid = fork();
402 15 50       353 croak "Cannot fork(): $!\n" if (not defined $childpid);
403            
404 15 100       144 if ($childpid == 0) {
405 6         324 debug("I'm the new child for $opt->{nannyname} $cmd[0] ...");
406             # child
407             # fill the info directory,
408            
409 6 50       1010 open(I, '>', $pdir . '/info') or croak "Cannot write '$pdir/info': $!\n";
410 6         28 my $start = time();
411 6         148 print I "_starttime=$start\n";
412 6         130 print I "_cmd=", join(",", map { s/([,\\])/\\$1/g; s/\n/\\n/g; $_ }
  6         154  
  6         68  
  6         80  
413             my @tmp = @cmd), "\n";
414 6         124 foreach my $o (keys(%$opt)) {
415 28         124 my $v = $opt->{$o};
416 28         28 $v =~ s/\n/\\n/sg;
417 28         54 $v =~ s/\r/\\r/sg;
418 28         94 print I "$o=$v\n";
419             }
420 6         372 close(I);
421              
422 6         526 open(STDIN, '/dev/null');
423 6 50       554 open(STDOUT, '>', $pdir . '/STDOUT') or
424             croak "Cannot write '$pdir/STDOUT': $!\n";
425 6 50       442 open(STDERR, '>', $pdir . '/STDERR') or
426             croak "Cannot write '$pdir/STDERR': $!\n";
427              
428 6 50       72 if ($opt->{_closefdmax} >= 3) {
429 6         44 for (my $fd = 3; $fd <= $opt->{_closefdmax}; $fd++) {
430 60 50 33     306 POSIX::close($fd)
431             unless ($_debugfh and $fd == fileno($_debugfh));
432             }
433             }
434            
435 6         64 $_unix_grandch_dir = $pdir;
436              
437 6         232 $0 = $opt->{nannyname} . ' ' . join(' ', @cmd);
438            
439 6         92 $SIG{CHLD} = \&_unix_chld_sighandler;
440            
441 6         4107 $_unix_grandch_pid = fork(); # fork again for grandchild
442 6 100       231 if ($_unix_grandch_pid == 0) {
443 3         240 debug("I'm the new grandchild for $cmd[0] ...");
444             #grandchild
445 3         78 $SIG{CHLD} = 'DEFAULT';
446 3         105 setpgrp(0, 0);
447             # Sometimes (esp. on AIX) it seems that the following exec
448             # can terminate before $_unix_grandch_pid is set in the
449             # parent, which is bad since the sighandler checks it...
450             # so wait for the pid file
451 3   100     68 my $psynctime = $opt->{psynctime} || 10;
452 3         25 my $waited = 0;
453 3         18 debug("waiting max $psynctime s for 'start' file");
454 3         23 for (my $nwait = 1; $nwait < 10 * $psynctime; $nwait++) {
455 3 50       133 last if (-e $pdir . "/pstart");
456 0         0 $waited++;
457 0         0 select(undef, undef, undef, 0.1);
458             }
459 3         281 unlink($pdir . "/pstart");
460 3         107 debug("waited " . (0.1 * $waited) . " s for 'start' file");
461 3         215 open(STA, '>', $pdir . "/start"); print STA time(), "\n"; close(STA);
  3         63  
  3         102  
462 3 0       0 if (not exec(@cmd)) {
463 0         0 open(FA, '>', $pdir . "/fail"); print FA "$!\n"; close(FA);
  0         0  
  0         0  
464 0         0 unlink($pdir . "/start");
465 0         0 croak "Could not exec(", join(', ', @cmd), ")\n";
466             }
467             }
468 3 50       32 croak "Cannot fork(): $!\n" if (not defined $_unix_grandch_pid);
469              
470 3         221 $SIG{INT} = \&_unix_term_sighandler;
471 3         50 $SIG{TERM} = \&_unix_term_sighandler;
472 3         27 $SIG{QUIT} = \&_unix_term_sighandler;
473              
474 3         276 open(PID, '>', $pdir . "/pid=$_unix_grandch_pid"); close(PID);
  3         36  
475 3         207 open(STA, '>', $pdir . "/pstart"); print STA time(), "\n"; close(STA);
  3         80  
  3         193  
476 3         132 debug("child says: \$_unix_grandch_pid = $_unix_grandch_pid " .
477             "for $cmd[0] ...");
478            
479 3 100       27 my $timeout = $opt->{timeout} ? int($opt->{timeout}) : 86400000;
480 3         6 my $maxtime = time + $timeout;
481              
482             # $_unix_grandch_pid will be set to undef in the sighandler...
483             # well, sometimes it seems it doesn't (some perls on some AIX, eg.)
484             # so we also test with kill(0, ...)
485 3         14 debug("Before wait loop for $cmd[0] ...");
486 3   66     160 while ($_unix_grandch_pid and kill(0, $_unix_grandch_pid) and time < $maxtime) {
      100        
487 3         13 my $remain = $maxtime - time;
488             # check each min to avoid race condition, see below
489 3 100       8 $remain = 60 if ($remain > 60);
490 3         241 debug("Waiting for grandchild... sleep($remain) at "
491             . scalar(localtime));
492 3 50       7711243 sleep($remain) # will be interrupted by SIGCHLD, this is what we want
493             # but if the sighandler jumps in exactly HERE
494             # we still have a race cond.
495             # That's why we sleep at most 60 s
496             if ($_unix_grandch_pid); # just to be sure...
497             }
498 3         201 debug("after wait loop for $cmd[0] ... at " . scalar(localtime));
499 3 100 66     36 if ($_unix_grandch_pid and kill(0, $_unix_grandch_pid)) {
500 1         770 carp "ProcPersCtrl:$$: Child $_unix_grandch_pid is alive after $timeout s, killing it\n";
501 1 50       320 open(I, '>>', $pdir . '/info') or
502             croak "Cannot append to '$pdir/info': $!\n";
503 1         13 print I "_timed_out=1\n";
504 1         86 close(I);
505 1         12 _unix_kill($_unix_grandch_pid);
506             }
507 3         288 exit(0);
508             }
509             # parent
510 9         496 debug("parent says: child $childpid");
511 9         152 return $childpid;
512             }
513              
514             sub _win_spawn {
515 0     0   0 my $pdir = shift;
516 0         0 my $opt = shift;
517 0         0 my @cmd = @_;
518              
519             # find the helper script
520 0         0 my $module_path = $INC{'Proc/PersistentControl.pm'};
521 0         0 $module_path =~ s|(.*)[/\\].*|$1|;
522 0         0 my $helper = '"' . $module_path . '/PersistentControl/winjob.pl' . '"';
523              
524             # construct args the DOS way, hopefully...
525 0         0 my $Arg = 'perl ' . $helper . " ";
526 0 0       0 $Arg .= ' -d ' if ($_debugfh);
527 0 0       0 my $timeout = $opt->{timeout} ? $opt->{timeout} : 0;
528 0 0       0 $Arg .= " -t $timeout " if ($timeout > 0);
529             # well, if you have a '"' inside one of the arguments, I'm sorry...
530 0         0 $Arg .= "\"$pdir\" \"" . join('" "', @cmd) . '"';
531              
532 0         0 debug("perl \$Arg = '$Arg'");
533              
534             # launch windows process (helper script, in background)
535 0         0 my $process;
536 0 0       0 Win32::Process::Create($process, $^X, $Arg, 0, 0, '.') or
537             croak "Cannot Win32::Process::Create(): $!\n";
538              
539 0         0 my $childpid = $process->GetProcessID();
540            
541 0         0 debug("\$childpid = $childpid");
542            
543 0 0       0 open(I, '>', $pdir . '/info') or croak "Cannot write '$pdir/info': $!\n";
544 0         0 my $start = time();
545 0         0 print I "_starttime=$start\n";
546 0         0 print I "_cmd=", join(",", map { s/([,\\])/\\$1/g; s/\n/\\n/g; $_ }
  0         0  
  0         0  
  0         0  
547             my @tmp = @cmd), "\n";
548 0         0 foreach my $o (keys(%$opt)) {
549 0         0 my $v = $opt->{$o};
550 0         0 $v =~ s/\n/\\n/sg;
551 0         0 $v =~ s/\r/\\r/sg;
552 0         0 print I "$o=$v\n";
553             }
554 0         0 close(I);
555            
556 0         0 return $childpid;
557             }
558              
559             sub _unix_kill {
560 2     2   17 my @pids = @_;
561 2         7 my @ret;
562 2         19 foreach my $p (@pids) {
563 2         13 debug("killing $p");
564             # first the process group if we have one
565 2 100 66     201794 kill(-15, $p) and
566             (select(undef, undef, undef, 0.2), kill -3, $p) and
567             (select(undef, undef, undef, 0.4), kill -9, $p);
568             # then a single process
569 2 50 33     27 kill( 15, $p) and
570             (select(undef, undef, undef, 0.2), kill 3, $p) and
571             (select(undef, undef, undef, 0.4), kill 9, $p);
572 2 50       45 push(@ret, $p) unless (kill(0, $p));
573             }
574 2         25 return @ret;
575             }
576              
577             sub _win_kill {
578 0     0   0 my @pids = @_;
579 0         0 foreach my $p (@pids) {
580 0         0 my $exitcode = 130;
581 0         0 debug("killing $p");
582 0         0 Win32::Process::KillProcess($p, $exitcode);
583             }
584 0         0 return @pids;
585             }
586              
587             ######################################################################
588             # internal functions
589              
590             sub _UID_alive($$) {
591 10     10   25 my $sdir = shift;
592 10         21 my $UID = shift;
593              
594 10         37 my $pdir = $sdir . '/' . $UID;
595 10         14 my $pid;
596             my $RC;
597            
598 10         453 opendir(D, $pdir);
599 10         241 while ($_ = readdir(D)) {
600 77 100       247 $pid = $1 if (m/^pid=(\d+)/);
601 77 100       318 $RC = $1 if (m/^RC=(\d+)/);
602             }
603 10         360 closedir(D);
604 10 100       102 return defined($RC) ? 0 : _pid_alive($pid);
605             }
606              
607             sub _get_all_uids {
608 0     0   0 my $sdir = shift;
609 0         0 my @ret;
610              
611 0 0       0 opendir(D, $sdir) or croak "Cannot opendir '$sdir': $!\n";
612 0         0 while (my $d = readdir(D)) {
613 0 0       0 if ($d =~ m/^PPC-/) {
614 0         0 push(@ret, $d);
615 0         0 debug("found job info dir \"$d\"");
616             }
617             }
618 0         0 closedir(D);
619 0         0 debug("returning " . join(' ', @ret));
620 0         0 return @ret;
621             }
622              
623             sub _get_pid_by_uid {
624 1     1   3 my $sdir = shift;
625 1         9 my $UID = shift;
626              
627 1         5 debug("in $sdir $UID");
628 1         4 my $pdir = $sdir . '/' . $UID;
629 1         1 my $pid;
630 1 50       39 if (opendir(DD, $pdir)) {
631 1         10 while (my $f = readdir(DD)) {
632 7         23 debug("read $f");
633 7 100       44 $pid = $1 if ($f =~ m/pid=(\d+)$/);
634             }
635 1         10 closedir(DD);
636 1 50       6 carp "Could not find pid file in $pdir\n" unless ($pid);
637             } else {
638 0         0 carp "Can't opendir $pdir for $UID\n";
639             }
640 1         9 debug("returning $pid");
641 1         5 return ($pid);
642             }
643              
644             sub _get_info_by_uid {
645 2     2   5 my $sdir = shift;
646 2         5 my $UID = shift;
647            
648 2         4 my %ret;
649              
650 2         8 my $pdir = $sdir . '/' . $UID;
651 2         7 my $infofile = $pdir . '/info';
652            
653 2 50       110 open(I, $infofile) or return undef;
654              
655 2         70 while () {
656 17         32 chomp;
657 17 50       190 $ret{$1} = $2 if (m/([^=]+)=(.*)/);
658             }
659 2         20 close(I);
660            
661 2         12 $ret{_dir} = $pdir;
662 2 50       13 $ret{_dir} =~ s|/|\\|g if ($^O =~ /^MSWin/); # cosmetics, also works without
663              
664 2   100     18 my $rctryt = $ret{psynctime} || 10;
665            
666 2         4 my $pidalive = 0;
667 2         12 debug("waiting max $rctryt s for 'RC' file");
668 2         16 for (my $ntry = 0; $ntry < 10 * $rctryt; $ntry++, select(undef, undef, undef, 0.1)) {
669             # try a few times, just in case this function is called
670             # after the process has terminated but before RC could be written
671             # nevertheless it's still a race condition...
672 2         13 debug("try " . ($ntry + 1) . " to get RC in $pdir");
673 2 50       64 opendir(D, $pdir) or croak "cannot opendir $pdir: $!";
674 2         21 while ($_ = readdir(D)) {
675 16         39 debug("readdir() gives: $_");
676 16 100       49 $ret{_pid} = $1 if (m/^pid=(\d+)/);
677 16 100       77 $ret{_RC} = $1 if (m/^RC=(\d+)/);
678             }
679 2         14 closedir(D);
680 2 50       10 last if (exists($ret{_RC})); # found ret code, fine
681 0 0       0 if ($ret{_pid}) {
682 0         0 $pidalive = _pid_alive($ret{_pid});
683 0 0       0 last if ($pidalive); # still alive, also ok
684             }
685 0         0 debug("No RC found in $pdir although process is dead...");
686             }
687            
688 2 50       9 if (exists($ret{_RC})) {
689 2         8 $ret{_alive} = 0;
690             } else {
691 0 0       0 if ($pidalive) {
692 0         0 $ret{_alive} = 1;
693             } else {
694             # no RC and not alive => terminated the hard way...
695 0         0 $ret{_alive} = 0;
696 0         0 debug("Could not determine RC, setting it to 130");
697 0         0 carp "Could not determine return code of job in $pdir, " .
698             "setting it to 130";
699 0         0 $ret{_RC} = 130;
700 0         0 open(RC, '>', $pdir . '/RC=130'); close(RC);
  0         0  
701             }
702             }
703            
704 7     7   42 no warnings "uninitialized";
  7         7  
  7         5516  
705 2         20 debug("\$ret{_RC} = $ret{_RC} \$ret{_dir} = $ret{_dir} " .
706             "\$ret{_pid} = $ret{_pid} \$ret{TAG} = $ret{TAG}");
707              
708 2         14 return \%ret;
709             }
710              
711             sub _make_uid_list {
712             # return a uid list for @in IDs
713 0     0   0 my $sdir = shift;
714 0         0 my @in = @_;
715 0         0 my @out;
716              
717 0         0 debug("in: ", join('-', @in));
718              
719             # empty input, return all UIDS
720 0 0       0 if (not $in[0]) {
721 0         0 @out = _get_all_uids($sdir);
722             } else {
723             # otherwise: return UIDS for input ids
724 0         0 foreach my $UID (@in) {
725 0         0 my $pdir = $sdir . '/' . $UID;
726 0 0       0 push(@out, $UID) if (-f $pdir . '/info');
727             }
728             }
729 0         0 debug("out: ", join('-', @out));
730 0         0 return @out;
731             }
732              
733             ######################################################################
734             # Proc::PersistentControl object methods, actually all of them return
735             # objects of class Proc::PersistentControl::Proc
736              
737             sub StartProc {
738             # this is one of the "new" method for
739             # Proc::PersistentControl::Proc
740            
741 15     15 1 464 my $self = shift;
742 15         27 my $opt = shift;
743 15         56 my @cmd = @_;
744              
745 15         93 my $sdir = $self->{sdir};
746              
747 15         29 my $w = "Invalid option to StartProc(): Option should not";
748 15         105 foreach my $o (keys(%$opt)) {
749 27 50       101 carp "$w contain '='" if ($o =~ m/=/);
750 27 50       106 carp "$w start with '_'" if ($o =~ m/^_/);
751             }
752            
753 15         202 debug("command '" . join(' ', @cmd) . "'");
754              
755             # create a directory for process information
756 15         180 my $psd = tempdir('PPC-XXXX', DIR => $sdir);
757 15 50 33     7924 croak "Cannot make tempdir: $!" unless ($psd and -d $psd);
758              
759 15         147 $psd =~ m/.*(PPC-.*)/;
760 15         87 my $UID = $1;
761              
762 15         42 $opt->{_PPCUID} = $UID;
763 15   33     111 $opt->{nannyname} ||= $self->{_opts}->{nannyname};
764 15         30 $opt->{_closefdmax} = $self->{_opts}->{closefdmax};
765            
766 15 50       104 ($^O !~ /^MSWin/) ?
767             _unix_spawn($psd, $opt, @cmd) :
768             _win_spawn( $psd, $opt, @cmd);
769              
770 9         228 my $Proc = {
771             _PPCUID => $UID,
772             _controller => $self
773             };
774              
775 9   100     139 my $psynctime = $opt->{psynctime} || 10;
776 9         36 my $waited = 0;
777 9         81 for (my $nwait = 1; $nwait < 10 * $psynctime; $nwait++) {
778 18 100 66     918 last if (-e $psd . "/start" or -e $psd . "/fail");
779 9         29 $waited++;
780 9         904629 select(undef, undef, undef, 0.1);
781             }
782 9         407 debug("waited " . (0.1 * $waited) . " s for 'start/fail' file");
783 9 50       364 if (-e $psd . "/fail") {
    50          
784 0 0       0 open(FA, $psd . "/fail") or die "Cannot read $psd/fail: $!\n";
785 0         0 my $st = ;
786 0         0 close(FA);
787 0         0 carp "StartProc could not start child process $cmd[0]: $st";
788 0         0 return undef;
789             } elsif (-e $psd . "/start") {
790 9         221 bless $Proc, 'Proc::PersistentControl::Proc';
791 9         1488 debug("returning Proc object for $UID, pid file " .
792             (glob($psd . '/pid=*'))[0]);
793 9         299 return $Proc;
794             } else {
795 0         0 carp "StartProc could not start child process after " .
796             (0.1 * $waited) . " s\n";
797 0         0 return undef;
798             }
799             }
800              
801             sub ProcList {
802             # this is also a "new" method for
803             # Proc::PersistentControl::Proc
804 0     0 1 0 my $self = shift;
805 0         0 my $key = shift;
806 0         0 my $val = shift;
807            
808 0         0 my @ret;
809              
810 0         0 debug("called from " . join('-', caller()));
811            
812 0         0 my @uidlist = _make_uid_list($self->{sdir});
813            
814 0         0 UID: foreach my $UID (@uidlist) {
815 0 0       0 if ($key) {
816 0         0 my $i = _get_info_by_uid($self->{sdir}, $UID);
817 0 0       0 next UID unless ($i->{$key} eq $val);
818             }
819 0         0 my $Proc = {
820             _PPCUID => $UID,
821             _controller => $self
822             };
823 0         0 bless $Proc, 'Proc::PersistentControl::Proc';
824 0         0 push(@ret, $Proc);
825             }
826 0         0 return(@ret);
827             }
828              
829             sub RipeList {
830             # another "new" method for
831             # Proc::PersistentControl::Proc
832            
833 0     0 1 0 my $self = shift;
834 0         0 my @ret;
835              
836 0         0 debug("called from " . join('-', caller()));
837            
838 0         0 my @uidlist = _make_uid_list($self->{sdir});
839              
840 0         0 foreach my $UID (@uidlist) {
841 0         0 debug("checking $UID");
842 0 0       0 if (not _UID_alive($self->{sdir}, $UID)) {
843 0         0 my $Proc = {
844             _PPCUID => $UID,
845             _controller => $self
846             };
847 0         0 bless $Proc, 'Proc::PersistentControl::Proc';
848 0         0 push(@ret, $Proc);
849 0         0 debug("$UID not alive, ready for reaping");
850             }
851             }
852 0         0 return(@ret);
853             }
854              
855             ######################################################################
856             ######################################################################
857              
858             package Proc::PersistentControl::Proc;
859 7     7   3290 use File::Copy;
  7         13034  
  7         4438  
860 7     7   35 use File::Path qw(mkpath rmtree); # legacy interface, works with older perls
  7         7  
  7         273  
861 7     7   21 use Carp;
  7         7  
  7         3654  
862              
863             sub _getUID {
864 0     0   0 my $self = shift;
865 0         0 return $self->{_PPCUID};
866             }
867              
868             sub Kill {
869 1     1   1000222 my $self = shift;
870              
871 1         22 Proc::PersistentControl::debug("called from " . join('-', caller()));
872            
873 1 50       21 return undef unless (Proc::PersistentControl::_UID_alive(
874             $self->{_controller}->{sdir},
875             $self->{_PPCUID}));
876              
877 1         23 my $pid = Proc::PersistentControl::_get_pid_by_uid(
878             $self->{_controller}->{sdir},
879             $self->{_PPCUID});
880            
881 1 50       26 my @ret = ($^O !~ /^MSWin/) ?
882             Proc::PersistentControl::_unix_kill(($pid)) :
883             Proc::PersistentControl::_win_kill( ($pid));
884              
885             # wait max 1 sec until process has finished and info is written
886 1         8 for (my $nwait = 0; $nwait < 10; $nwait++) {
887 1 50       8 last if (not $self->IsAlive());
888 0         0 Proc::PersistentControl::debug("Process not dead after Kill() ($nwait)...");
889 0         0 select(undef, undef, undef, 0.1);
890             }
891 1 50       5 carp "Something is strange, Kill()ed process seems to be still alive..."
892             if ($self->IsAlive());
893            
894 1         6 return $ret[0];
895             }
896              
897             sub Info {
898 0     0   0 my $self = shift;
899              
900 0         0 Proc::PersistentControl::debug("called from " . join('-', caller()));
901            
902 0         0 return Proc::PersistentControl::_get_info_by_uid(
903             $self->{_controller}->{sdir},
904             $self->{_PPCUID});
905             }
906              
907             sub IsAlive {
908 9     9   1000978 my $self = shift;
909            
910 9         67 return Proc::PersistentControl::_UID_alive(
911             $self->{_controller}->{sdir},
912             $self->{_PPCUID});
913             }
914              
915             sub IsRipe {
916 3     3   70 my $self = shift;
917 3         14 return not $self->IsAlive();
918             }
919              
920             sub Reap {
921 2     2   109 my $self = shift;
922              
923 2         21 Proc::PersistentControl::debug("called from " . join('-', caller()));
924            
925 2 50       9 return undef unless ($self->IsRipe());
926              
927 2         10 my $sdir = $self->{_controller}->{sdir};
928 2         6 my $UID = $self->{_PPCUID};
929            
930 2         7 my $source = $sdir . '/' . $UID;
931 2         6 my $target = $sdir . '/reaped/' . $UID;
932              
933 2 50       44 if (not -d $target) {
934 2 0 33     200 rename($source, $target) or
      33        
      0        
935             move($source, $target) or
936             # sometimes (on windows) the dir seems to be "locked"
937             # also after process termination etc.
938             (Proc::PersistentControl::debug("rename/1st move($source, $target) failed, trying again"),
939             select(undef, undef, undef, 0.2),
940             move($source, $target)) or
941             (Proc::PersistentControl::debug("2nd move($source, $target) failed, trying again"),
942             select(undef, undef, undef, 0.8),
943             move($source, $target)) or
944             (Proc::PersistentControl::debug("3rd move($source, $target) failed"));
945             # do not check return value of move
946             # this is unreliable, maybe (on windows...)
947 2 50       46 carp "Cannot move('$source', '$target'): $!\n" unless (-d $target);
948             } else {
949             ### Reap called twice???
950 0         0 carp "Reap: '$target' already exists, Reap() called twice?\n"
951             }
952 2         15 return Proc::PersistentControl::_get_info_by_uid($sdir . '/reaped', $UID);
953             }
954              
955             ######################################################################
956             sub DESTROY {
957 6     6   175 my $self = shift;
958              
959 6         99 my $pdir = $self->{_controller}->{sdir} . '/reaped/' . $self->{_PPCUID};
960              
961 6 100       180 if (-d $pdir) {
962 2 50       2725 rmtree($pdir) or
963             carp "Could not rmtree($pdir): $!";
964             }
965 6         487 return 1;
966             }
967              
968             1;