File Coverage

blib/lib/Proc/Fork/Control.pm
Criterion Covered Total %
statement 16 178 8.9
branch 3 98 3.0
condition 0 19 0.0
subroutine 4 24 16.6
pod 18 18 100.0
total 41 337 12.1


line stmt bran cond sub pod time code
1             package Proc::Fork::Control;
2              
3             #
4             # Copyright (C) 2014 Colin Faber
5             #
6             # This program is free software: you can redistribute it and/or modify
7             # it under the terms of the GNU General Public License as published by
8             # the Free Software Foundation version 2 of the License.
9             #
10             # This program is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13             # GNU General Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License
16             # along with this program. If not, see .
17             #
18             #
19             # Original author: Colin Faber
20             # Original creation date: 10/02/2014
21             # Version: $Id: Control.pm,v 1.4 2015/11/09 20:33:26 cfaber Exp $
22             #
23              
24             # Version number - cvs automagically updated.
25             our $VERSION = $1 if('$Revision: 1.4 $' =~ /: ([\d\.]+) \$/);
26              
27 1     1   15660 use strict;
  1         2  
  1         31  
28 1     1   864 use POSIX ('WNOHANG','setsid');
  1         8127  
  1         13  
29 1     1   2271 use Time::HiRes 'usleep';
  1         1475  
  1         4  
30              
31             require Exporter;
32              
33             # Exported routines
34             our @ISA = ('Exporter');
35             our @EXPORT = qw(
36             cfork
37             cfork_wait
38             cfork_init
39             cfork_exit
40             cfork_exit_code
41             cfork_maxchildren
42             cfork_errstr
43             cfork_is_child
44             cfork_has_children
45             cfork_nonblocking
46             cfork_daemonize
47             cfork_sleep
48             cfork_usleep
49             cfork_ssleep
50             cfork_active_children
51             cfork_kill_children
52             cfork_list_children
53             cfork_child_dob
54             );
55              
56             # Defaults
57             &cfork_init;
58              
59             require 5.008;
60              
61             $Proc::Fork::Control::VERSION = '$Revision: 1.4 $';
62              
63             =head1 NAME
64              
65             Proc::Fork::Control
66              
67             =head1 DESCRIPTION
68              
69             Proc::Fork::Control is a simple to use library which functions much the same way
70             as Proc::Fork. That said, Proc::Fork is not used, as fork() is accessed directly.
71              
72             Proc::Fork::Control allows you to manage forks, control number of children
73             allowed, monitor children, control blocking and nonblocking states, etc.
74              
75             =head1 SYNOPSIS
76              
77             #!/usr/bin/perl
78             use Proc::Fork::Control;
79             use Fcntl ':flock';
80              
81             # Initialize the system allowing 25 forks per cfork() level
82             cfork_init(25);
83              
84             for(my $i = 0; $i < 50; $i++){
85             # Fork this if possible, if all avaliable fork slots are full
86             # block until one becomes avaliable.
87             cfork(sub {
88             # Initialize for children
89             cfork_init(2);
90              
91             for('A' .. 'Z'){
92             cfork(sub {
93             # Lock STDOUT for writing.
94             flock(STDOUT, &LOCK_EX);
95              
96             # Print out a string.
97             print STDOUT "Fork: $i: $_\n";
98            
99             # Unlock STDOUT.
100             flock(STDOUT, &LOCK_UN);
101              
102             cfork_exit();
103             });
104             }
105              
106             # Wait for sub children to exit
107             cfork_wait()
108              
109             });
110             }
111              
112             # Wait until all forks have finished.
113             cfork_wait();
114              
115             =head1 METHODS
116              
117             Note - because of the nature of forking within perl. I've against objectifying this library. Rather it uses direct function calls which are exported to the global namespace Below is a list of these calls and how to access them.
118              
119             =head2 cfork(code, code, code)
120              
121             Provide managed forking functions.
122              
123             Returns nothing on error and sets the cfork_errstr error handler.
124              
125             if cfork() is called with in an cfork()ed process the calling cfork() process will block until all children with in it die off.
126              
127             =cut
128              
129             sub cfork {
130 0     0 1 0 _errstr();
131 0 0       0 if(!$Proc::Fork::Control::HEAP->{max_children}){
132 0         0 return _errstr("cfork_init() not set");
133             }
134              
135 0 0       0 if(!defined $Proc::Fork::Control::HEAP->{children}){
136 0         0 $Proc::Fork::Control::HEAP->{children} = 0;
137             }
138              
139 0 0       0 if(!defined $Proc::Fork::Control::HEAP->{max_children}){
140 0         0 $Proc::Fork::Control::HEAP->{max_children} = 0;
141             }
142              
143 0         0 my ($i, $delay);
144 0         0 while(1){
145 0         0 my $cl =_cleanup();
146              
147 0 0       0 if($Proc::Fork::Control::HEAP->{children} < $Proc::Fork::Control::HEAP->{max_children}){
148 0         0 last;
149             }
150              
151 0 0       0 if($Proc::Fork::Control::HEAP->{children}){
152 0 0       0 if($cl){
153             # There are still children alive, and we've cleaned up at least 1
154             # child on the last iteration so don't delay at all.
155 0         0 $i = 0;
156 0         0 $delay = 0;
157             } else {
158             # There are still children alive, delay based on the number of interations
159             # Minimum delay time (in micro seconds)
160 0   0     0 $delay ||= 5;
161              
162             # iterator for delay multiplication.
163 0         0 $i++;
164              
165 0         0 $delay = $delay * $i;
166              
167             # Maximum delay value
168 0 0       0 $delay = ($delay > 5000 ? 5000 : $delay);
169              
170             # sleep for a while..
171 0         0 cfork_usleep($delay);
172             }
173             }
174             }
175              
176 0 0       0 if($Proc::Fork::Control::HEAP->{is_child}){
177 0         0 $Proc::Fork::Control::HEAP->{has_children} = 1;
178             }
179              
180 0         0 my $pid = fork;
181 0 0       0 if($pid < 0){
    0          
182 0         0 return _errstr('fork failed: ' . $!);
183             } elsif($pid){
184             # This probably should use CLOCK_MONOTONIC time here, but it's not a big deal.
185 0         0 $Proc::Fork::Control::HEAP->{cidlist}->{$pid} = time();
186 0         0 $Proc::Fork::Control::HEAP->{children}++;
187             } else {
188 0         0 cfork_init();
189 0         0 $Proc::Fork::Control::HEAP->{is_child} = 1;
190 0         0 $SIG{PIPE} = 'IGNORE';
191 0         0 for my $code (@_){
192 0 0       0 if(ref($code) eq 'CODE'){
193 0         0 &{ $code };
  0         0  
194             }
195             }
196              
197 0         0 cfork_exit(2);
198             }
199              
200             # Wait for children to finish (if nonblocking
201 0         0 cfork_wait();
202              
203             # Return our PID for further use.
204 0         0 return $pid;
205             }
206              
207             =head2 cfork_nonblocking(BOOL)
208              
209             Set the cfork() behavior to nonblocking mode if is true, This will result in the fork returning right away rather than waiting for any possible children to die.
210              
211             Also, cfork_nonblocking() should always be turned off after the bit of code you want to run, runs.
212              
213             =item EXAMPLE
214              
215             cfork_nonblocking(0);
216              
217             cfork(sub {
218             do some work;
219             });
220              
221             cfork_nonblocking(1);
222              
223             =cut
224              
225             sub cfork_nonblocking {
226 0     0 1 0 $Proc::Fork::Control::HEAP->{nonblocking} = $_[0];
227             }
228              
229             =head2 cfork_is_child()
230              
231             Return true if called with in a forked enviroment, otherwise return false.
232              
233             =cut
234              
235             sub cfork_is_child {
236 0     0 1 0 return $Proc::Fork::Control::HEAP->{is_child};
237             }
238              
239             =head2 cfork_has_children()
240              
241             Return true if children exist with in a forked enviroment.
242              
243             =cut
244              
245             sub cfork_has_children {
246 0     0 1 0 return $Proc::Fork::Control::HEAP->{has_children};
247             }
248              
249             =head2 cfork_errstr()
250              
251             Return the last error message.
252              
253             =cut
254              
255             sub cfork_errstr {
256 0     0 1 0 my ($err) = @_;
257 0 0       0 $Proc::Fork::Control::errstr = $err if $err;
258 0         0 return $Proc::Fork::Control::errstr;
259             }
260              
261             sub _errstr {
262 0     0   0 my ($err) = @_;
263 0         0 cfork_errstr($err);
264 0         0 return;
265             }
266              
267             =head2 cfork_init(children)
268              
269             Initialize the CHLD reaper with a maximum number of
270              
271             This should be called prior to any cfork() calls
272              
273             =cut
274              
275             sub cfork_init {
276 1     1 1 2 my $ic = $Proc::Fork::Control::HEAP->{is_child};
277              
278 1         2 $Proc::Fork::Control::HEAP = {};
279 1         3 $Proc::Fork::Control::HEAP->{children} = 0;
280 1         2 $Proc::Fork::Control::HEAP->{cidlist} = {};
281 1 50       4 $Proc::Fork::Control::HEAP->{is_child} = ($ic ? 1 : 0);
282              
283 1 50       19 $SIG{CHLD} = \&Proc::Fork::Control::_sigchld if !$ic;
284              
285 1 50       7 if($_[0]){
286 0           $Proc::Fork::Control::HEAP->{max_children} = $_[0];
287             }
288             }
289              
290             =head2 cfork_exit(int)
291              
292             Exit a process cleanly and set an exit code.
293              
294             Normally this can be easily handled with $? however, in some cases $? is not reliably delivered.
295              
296             Once called, drop to END {} block and terminate.
297              
298             =cut
299              
300             sub cfork_exit {
301 0     0 1   my ($exit) = @_;
302 0           $Proc::Fork::Control::HEAP->{exit} = $exit;
303 0           exit($exit);
304             }
305              
306             =head2 cfork_exit_code()
307              
308             Returns the last known cfork_exit() code.
309              
310             =cut
311              
312             sub cfork_exit_code {
313 0     0 1   return $Proc::Fork::Control::HEAP->{exit};
314             }
315              
316             =head2 cfork_maxchildren(int)
317              
318             Set/Reset the maximum number of children allowed.
319              
320             =cut
321              
322             sub cfork_maxchildren {
323 0 0   0 1   $Proc::Fork::Control::HEAP->{max_children} = $_[0] if $_[0];
324             }
325              
326             =head2 cfork_wait()
327              
328             Block until all cfork() children have died off unless cfork_nonblocking() is enabled.
329              
330             =cut
331              
332             sub cfork_wait {
333 0     0 1   my ($to) = @_;
334 0 0         return 1 if $Proc::Fork::Control::HEAP->{nonblocking};
335              
336 0 0         $to = time + $to if $to;
337              
338 0           my ($i, $delay);
339 0           while(1){
340 0           my $cl =_cleanup();
341              
342 0 0 0       if(!$Proc::Fork::Control::HEAP->{children}){
    0          
343 0           last;
344             } elsif($to && time >= $to){
345 0           last;
346             }
347              
348 0 0         if($Proc::Fork::Control::HEAP->{children}){
349 0 0         if($cl){
350             # There are still children alive, and we've cleaned up at least 1
351             # child on the last iteration so don't delay at all.
352 0           $i = 0;
353 0           $delay = 0;
354             } else {
355             # There are still children alive, delay based on the number of interations
356             # Minimum delay time (in micro seconds)
357 0   0       $delay ||= 5;
358              
359             # iterator for delay multiplication.
360 0           $i++;
361              
362 0           $delay = $delay * $i;
363              
364             # Maximum delay value
365 0 0         $delay = ($delay > 5000 ? 5000 : $delay);
366              
367             # sleep for a while..
368 0           cfork_usleep($delay);
369             }
370             }
371             }
372              
373 0           return 1;
374             }
375              
376             =head2 cfork_active_children()
377              
378             Return the total number of active children.
379              
380             =cut
381              
382             sub cfork_active_children {
383 0     0 1   _cleanup();
384 0 0         return ($Proc::Fork::Control::HEAP->{children} ? $Proc::Fork::Control::HEAP->{children} : 0);
385             }
386              
387             =head2 cfork_daemonize(BOOL)
388              
389             Daemonize the the calling script.
390              
391             If is true write _ALL_ output to /dev/null.
392              
393             If you have termination handling, i.e. %SIG and END {} block control, cfork_daemonize triggers exit signal 2. So... $? == 4
394              
395             =cut
396              
397             sub cfork_daemonize {
398 0     0 1   my $q = $_[0];
399 0 0         chdir('/') || die "Can't chdir to /: $!\n";
400 0 0         if(!$q){
401 0           open STDIN, '/dev/null' || die "Can't read /dev/null: $!\n";
402 0           open STDOUT, '>/dev/null' || die "Can't write to /dev/null: $!\n";
403 0           open STDERR, '>&STDOUT' || die "Can't dup stdout: $!";
404             }
405              
406 0 0         defined(my $pid = fork) || die "Can't fork: $!\n";
407 0 0         cfork_exit(4) if $pid;
408 0 0         setsid || die "Can't start a new session: $!\n";
409             }
410              
411             =head2 cfork_sleep(int)
412              
413             Provides an alarm safe sleep() wrapper. Beacuse we sleep() with in this, ALRM will be issued with in the fork once the sleep cycle has completed. This function wraps sleep with in a while() block and tests to make sure that the seconds requested for the sleep were slept.
414              
415             =cut
416              
417             sub cfork_sleep {
418 0     0 1   my $sleep = $_[0];
419 0 0         return if $sleep !~ /^\d+$/;
420              
421 0           my $sleeper = 0;
422 0           my $slept = 0;
423              
424 0           while(1){
425 0 0 0       if($sleeper < 0 || $sleep <= 0){
    0          
426 0           last;
427             } elsif(!$sleeper) {
428 0           $sleeper = $sleep;
429             }
430              
431 0           my $remain = sleep( abs($sleeper) );
432              
433 0 0 0       if($remain ne $sleeper && $remain < $sleep){
434 0           $slept += $remain;
435 0           $sleeper = $sleeper - $remain;
436              
437 0           next;
438             } else {
439 0           last;
440             }
441             }
442              
443 0           return $slept;
444             }
445              
446             =head2 cfork_usleep(int)
447              
448             Provides an alarm safe Time::HiRes usleep() wrapper. Beacuse we sleep() with in this, ALRM will be issued with in the fork once the sleep cycle has completed. This function wraps sleep with in a while() block and tests to make sure that the seconds requested for the sleep were slept.
449              
450             This function is only avaliable if Time::HiRes is avaliable otherwise it will simply return nothing at all.
451              
452             =cut
453              
454             sub cfork_usleep {
455 0     0 1   my $sleep = $_[0];
456              
457 0           my $sleeper = 0;
458 0           my $slept = 0;
459              
460 0           while(1){
461 0 0 0       if($sleeper < 0 || $sleep <= 0){
    0          
462 0           last;
463             } elsif(!$sleeper) {
464 0           $sleeper = $sleep;
465             }
466              
467 0           my $remain = usleep( abs($sleeper) );
468              
469 0 0 0       if($remain ne $sleeper && $remain < $sleep){
470 0           $slept += $remain;
471 0           $sleeper = $sleeper - $remain;
472              
473 0           next;
474             } else {
475 0           last;
476             }
477             }
478              
479 0           return $slept;
480             }
481              
482             =head2 cfork_ssleep(int)
483              
484             Preform an cfork_sleep() except rather than using standard sleep() (with interruption handling) use a select() call to sleep. This can be useful in environments where sleep() does not behave correctly, and a select() will block for the desired number of seconds properly.
485              
486             =cut
487              
488             sub cfork_ssleep {
489 0     0 1   $Proc::Fork::Control::HEAP->{select_sleep} = 1;
490 0           my $r = cfork_sleep(@_);
491 0           $Proc::Fork::Control::HEAP->{select_sleep} = 0;
492 0           return $r;
493             }
494              
495             =head2 cfork_kill_children(SIGNAL)
496              
497             Send all children (if any) this .
498              
499             If the argument is omitted kill TERM will be used.
500              
501             =cut
502              
503             sub cfork_kill_children {
504 0     0 1   my $sig = $_[0];
505 0           _cleanup();
506 0 0         if(!$sig){
507 0           $sig = 'TERM';
508             }
509              
510 0 0         if($Proc::Fork::Control::HEAP->{cidlist}){
511 0           kill($sig, keys %{ $Proc::Fork::Control::HEAP->{cidlist} });
  0            
512             }
513             }
514              
515             =head2 cfork_list_children(BOOL)
516              
517             Return a list of PID's currently running under this fork.
518              
519             If BOOL is true a hash will be returned rather than a list.
520              
521             =cut
522              
523             sub cfork_list_children {
524 0     0 1   my ($use_hash) = @_;
525 0           _cleanup();
526              
527 0 0         if(!$Proc::Fork::Control::HEAP->{cidlist}){
528 0           return;
529             }
530              
531 0 0         if($use_hash){
532 0           return (%{ $Proc::Fork::Control::HEAP->{cidlist} });
  0            
533             } else {
534 0           return keys %{ $Proc::Fork::Control::HEAP->{cidlist} };
  0            
535             }
536             }
537              
538             =head2 cfork_child_dob(PID)
539              
540             Return the EPOCH Date of Birth for this childs
541              
542             Returns 0 if no child exists under that PID for this fork.
543              
544             =cut
545              
546             sub cfork_child_dob {
547 0     0 1   my $pid = $_[0];
548 0           _cleanup();
549 0 0         if($Proc::Fork::Control::HEAP->{cidlist}->{$pid}){
550 0           return $Proc::Fork::Control::HEAP->{cidlist}->{$pid};
551             } else {
552 0           return;
553             }
554             }
555              
556             # Child handler
557             sub _sigchld {
558 0     0     my $our;
559 0           while((my $p = waitpid(-1, WNOHANG)) > 0){
560             # Mark the process is done ONLY if it's one of our processes.
561 0 0         if($Proc::Fork::Control::HEAP->{cidlist}->{$p}){
562 0           $Proc::Fork::Control::HEAP->{cidlist}->{$p} = 0;
563 0           $our = 1;
564             }
565             }
566              
567             # self reference only if it's one of our processes.
568 0 0         if($our){
569 0           $SIG{CHLD} = \&Proc::Fork::Control::_sigchld;
570             }
571             }
572              
573              
574             # clean up lists - thanks to gmargo@perlmonks for this idea.
575             sub _cleanup {
576 0     0     my $i = 0;
577 0           my @dpid = grep { $Proc::Fork::Control::HEAP->{cidlist}->{$_} == 0 } keys %{ $Proc::Fork::Control::HEAP->{cidlist} };
  0            
  0            
578 0 0         if(@dpid){
579 0           for(@dpid){
580 0 0         if(exists $Proc::Fork::Control::HEAP->{cidlist}->{$_}){
581 0           delete $Proc::Fork::Control::HEAP->{cidlist}->{$_};
582 0           $i++;
583              
584 0 0         $Proc::Fork::Control::HEAP->{children} -- if $Proc::Fork::Control::HEAP->{children};
585             }
586             }
587             }
588              
589             # Do some additional checks to see if these children are really alive.
590 0           for(keys %{ $Proc::Fork::Control::HEAP->{cidlist} }){
  0            
591 0 0         if(!kill(0, $_)){
592 0 0         if(exists $Proc::Fork::Control::HEAP->{cidlist}->{$_}){
593 0           delete $Proc::Fork::Control::HEAP->{cidlist}->{$_};
594 0           $i++;
595              
596 0 0         $Proc::Fork::Control::HEAP->{children} -- if $Proc::Fork::Control::HEAP->{children};
597             }
598             }
599             }
600              
601 0           return $i;
602             }
603              
604             1;