File Coverage

blib/lib/MCE/Signal.pm
Criterion Covered Total %
statement 89 172 51.7
branch 42 144 29.1
condition 25 113 22.1
subroutine 17 25 68.0
pod 3 3 100.0
total 176 457 38.5


line stmt bran cond sub pod time code
1             ###############################################################################
2             ## ----------------------------------------------------------------------------
3             ## Temporary directory creation/cleanup and signal handling.
4             ##
5             ###############################################################################
6              
7             package MCE::Signal;
8              
9 97     97   290624 use strict;
  97         271  
  97         3141  
10 97     97   502 use warnings;
  97         370  
  97         3168  
11              
12 97     97   467 no warnings qw( threads recursion uninitialized once );
  97         231  
  97         9806  
13              
14             our $VERSION = '1.888';
15              
16             ## no critic (BuiltinFunctions::ProhibitStringyEval)
17              
18             our ($display_die_with_localtime, $display_warn_with_localtime);
19             our ($main_proc_id, $prog_name, $tmp_dir);
20              
21             tie $tmp_dir, 'MCE::Signal::_tmpdir';
22              
23 97     97   689 use Carp ();
  97         290  
  97         10032  
24              
25             BEGIN {
26 97     97   567 $main_proc_id = $$;
27 97         228 $prog_name = $0;
28 97         677 $prog_name =~ s{^.*[\\/]}{}g;
29 97 50 33     1131 $prog_name = 'perl' if ($prog_name eq '-e' || $prog_name eq '-');
30              
31 97         2881 return;
32             }
33              
34 97     97   615 use base qw( Exporter );
  97         698  
  97         11882  
35 97     97   3992 use Time::HiRes ();
  97         7539  
  97         115254  
36              
37             our @EXPORT_OK = qw( $tmp_dir sys_cmd stop_and_exit );
38             our %EXPORT_TAGS = (
39             all => \@EXPORT_OK,
40             tmp_dir => [ qw( $tmp_dir ) ]
41             );
42              
43             END {
44 97 50 66 97   6436 MCE::Signal->stop_and_exit($?)
      66        
45             if ($$ == $main_proc_id && !$MCE::Signal::KILLED && !$MCE::Signal::STOPPED);
46             }
47              
48             ###############################################################################
49             ## ----------------------------------------------------------------------------
50             ## Process import, export, & module arguments.
51             ##
52             ###############################################################################
53              
54 0     0   0 sub _croak { $\ = undef; goto &Carp::croak }
  0         0  
55 0     0   0 sub _usage { _croak "MCE::Signal error: ($_[0]) is not a valid option" }
56 1     1   5 sub _flag { 1 }
57              
58             my $_is_MSWin32 = ($^O eq 'MSWin32') ? 1 : 0;
59             my $_keep_tmp_dir = 0;
60             my $_use_dev_shm = 0;
61             my $_no_kill9 = 0;
62             my $_imported;
63              
64             sub import {
65 4     4   44 my $_class = shift;
66 4 50       16 return if $_imported++;
67              
68 4         10 my ($_no_setpgrp, $_no_sigmsg, $_setpgrp, @_export_args) = (0, 0, 0);
69              
70 4         14 while (my $_arg = shift) {
71 7 50 0     18 $_setpgrp = _flag() and next if ($_arg eq '-setpgrp');
72 7 50 0     13 $_keep_tmp_dir = _flag() and next if ($_arg eq '-keep_tmp_dir');
73 7 100 50     14 $_use_dev_shm = _flag() and next if ($_arg eq '-use_dev_shm');
74 6 50 0     13 $_no_kill9 = _flag() and next if ($_arg eq '-no_kill9');
75              
76             # deprecated options for backwards compatibility
77 6 50 0     14 $_no_setpgrp = _flag() and next if ($_arg eq '-no_setpgrp');
78 6 50 0     11 $_no_sigmsg = _flag() and next if ($_arg eq '-no_sigmsg');
79              
80 6 50       25 _usage($_arg) if ($_arg =~ /^-/);
81              
82 6         69 push @_export_args, $_arg;
83             }
84              
85 4         8 local $Exporter::ExportLevel = 1;
86 4         329 Exporter::import($_class, @_export_args);
87              
88             ## Sets the current process group for the current process.
89 4 50 33     21 setpgrp(0,0) if ($_setpgrp == 1 && !$_is_MSWin32);
90              
91             ## Make tmp_dir if caller requested it.
92 4 100 100     32 _make_tmpdir() if ($_use_dev_shm || grep /tmp_dir/, @_export_args);
93              
94 4         57 return;
95             }
96              
97             ###############################################################################
98             ## ----------------------------------------------------------------------------
99             ## Configure signal handling.
100             ##
101             ###############################################################################
102              
103             ## Set traps to catch signals.
104             if ( !$_is_MSWin32 ) {
105             $SIG{ABRT} = \&stop_and_exit; # UNIX SIG 6
106             $SIG{HUP} = \&stop_and_exit; # UNIX SIG 1
107             $SIG{INT} = \&stop_and_exit; # UNIX SIG 2
108             $SIG{PIPE} = \&stop_and_exit; # UNIX SIG 13
109             $SIG{QUIT} = \&stop_and_exit; # UNIX SIG 3
110             $SIG{TERM} = \&stop_and_exit; # UNIX SIG 15
111              
112             ## MCE handles the reaping of its children.
113             $SIG{CHLD} = 'DEFAULT';
114             }
115              
116             my $_safe_clean = 0;
117              
118             sub _make_tmpdir {
119 34     34   136 my ($_count, $_tmp_base_dir) = (0);
120              
121 34 0 33     244 return $tmp_dir if (defined $tmp_dir && -d $tmp_dir && -w _);
      33        
122              
123 34 50 33     306 if ($ENV{TEMP} && -d $ENV{TEMP} && -w _) {
      33        
124 0 0       0 if ($^O =~ /mswin|mingw|msys|cygwin/i) {
125 0         0 $_tmp_base_dir = $ENV{TEMP} . '/Perl-MCE';
126 0 0       0 mkdir $_tmp_base_dir unless -d $_tmp_base_dir;
127             } else {
128 0         0 $_tmp_base_dir = $ENV{TEMP};
129             }
130             }
131             else {
132 34 100 66     257 $_tmp_base_dir = ($_use_dev_shm && -d '/dev/shm' && -w _)
133             ? '/dev/shm' : '/tmp';
134             }
135              
136             _croak("Error: MCE::Signal: ($_tmp_base_dir) is not writeable")
137 34 50 33     1024 if (! exists $ENV{'MOBASTARTUPDIR'} && ! -w $_tmp_base_dir);
138              
139             ## Remove tainted'ness from $tmp_dir.
140 34         652 ($tmp_dir) = "$_tmp_base_dir/$prog_name.$$.$_count" =~ /(.*)/;
141              
142 34         158 while ( !(mkdir $tmp_dir, 0770) ) {
143 0         0 ($tmp_dir) = ("$_tmp_base_dir/$prog_name.$$.".(++$_count)) =~ /(.*)/;
144             }
145              
146 34         233 $_safe_clean = 1;
147              
148 34         158 return $tmp_dir;
149             }
150              
151             sub _remove_tmpdir {
152 9 50 33 9   34 return if (!defined $tmp_dir || $tmp_dir eq '' || ! -d $tmp_dir);
      33        
153              
154 9 50       90 if ($_keep_tmp_dir == 1) {
    50          
155 0         0 print {*STDERR} "$prog_name: saved tmp_dir = $tmp_dir\n";
  0         0  
156             }
157             elsif ($_safe_clean) {
158 9 50 33     206 if ($ENV{'TEMP'} && $^O =~ /mswin|mingw|msys|cygwin/i) {
159             ## remove tainted'ness
160 0         0 my ($_dir) = $ENV{'TEMP'} =~ /(.*)/;
161 0 0       0 chdir $_dir if -d $_dir;
162             }
163 9         62 rmdir $tmp_dir;
164 9 50       107 if (-d $tmp_dir) {
165 0         0 local $@; local $SIG{__DIE__};
  0         0  
166 0         0 eval 'require File::Path; File::Path::rmtree($tmp_dir)';
167             }
168             }
169              
170 9         113 $tmp_dir = undef;
171             }
172              
173             ###############################################################################
174             ## ----------------------------------------------------------------------------
175             ## Stops execution, removes temp directory and exits cleanly.
176             ##
177             ## Provides safe reentrant logic for parent and child processes.
178             ## The $main_proc_id variable is defined above.
179             ##
180             ###############################################################################
181              
182             BEGIN {
183 97     97   415 $MCE::Signal::IPC = 0; # 1 = defer signal_handling until completed IPC
184 97         180582 $MCE::Signal::SIG = ''; # signal received during IPC in MCE::Shared 1.863
185             }
186              
187             sub defer {
188 0 0   0 1 0 $MCE::Signal::SIG = $_[0] if $_[0];
189 0         0 return;
190             }
191              
192             my %_sig_name_lkup = map { $_ => 1 } qw(
193             __DIE__ ABRT HUP INT PIPE QUIT TERM __WARN__
194             );
195              
196             my $_count = 0;
197              
198             my $_handler_count = $INC{'threads/shared.pm'}
199             ? threads::shared::share($_count)
200             : \$_count;
201              
202             sub stop_and_exit {
203 22 50 33 22 1 596 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
204 22 50       148 return MCE::Signal::defer($_[0]) if $MCE::Signal::IPC;
205              
206 22   50     281 my ($_exit_status, $_is_sig, $_sig_name) = ($?, 0, $_[0] || 0);
207 22     0   528 $SIG{__DIE__} = $SIG{__WARN__} = sub {};
208              
209 22 50       232 if (exists $_sig_name_lkup{$_sig_name}) {
210 0         0 $_exit_status = $MCE::Signal::KILLED = $_is_sig = 1;
211 0 0       0 $_exit_status = 255, $_sig_name = 'TERM' if ($_sig_name eq '__DIE__');
212 0 0       0 $_exit_status = 0 if ($_sig_name eq 'PIPE');
213 0     0   0 $SIG{INT} = $SIG{$_sig_name} = sub {};
214             }
215             else {
216 22 50       449 $_exit_status = $_sig_name if ($_sig_name =~ /^\d+$/);
217 22         95 $MCE::Signal::STOPPED = 1;
218             }
219              
220             ## Main process.
221 22 50       143 if ($$ == $main_proc_id) {
    0          
222              
223 22 50       66 if (++${ $_handler_count } == 1) {
  22         167  
224             ## Kill process group if signaled.
225 22 50       160 if ($_is_sig == 1) {
226 0 0       0 ($_sig_name eq 'PIPE')
    0          
    0          
227             ? CORE::kill('PIPE', $_is_MSWin32 ? -$$ : -getpgrp)
228             : CORE::kill('INT' , $_is_MSWin32 ? -$$ : -getpgrp);
229              
230 0 0       0 if ($_sig_name eq 'PIPE') {
231 0         0 for my $_i (1..2) { Time::HiRes::sleep(0.015); }
  0         0  
232             } else {
233 0         0 for my $_i (1..3) { Time::HiRes::sleep(0.060); }
  0         0  
234             }
235             }
236              
237             ## Remove temp directory.
238 22 100       451 _remove_tmpdir() if defined($tmp_dir);
239              
240             ## Signal process group to die.
241 22 50       179 if ($_is_sig == 1) {
242 0 0 0     0 if ($_sig_name eq 'INT' && -t STDIN) { ## no critic
243 0         0 print {*STDERR} "\n";
  0         0  
244             }
245 0 0 0     0 if ($INC{'threads.pm'} && ($] lt '5.012000' || threads->tid())) {
      0        
246 0 0 0     0 ($_no_kill9 == 1 || $_sig_name eq 'PIPE')
    0          
247             ? CORE::kill('INT', $_is_MSWin32 ? -$$ : -getpgrp)
248             : CORE::kill('KILL', -$$);
249             }
250             else {
251 0 0       0 CORE::kill('INT', $_is_MSWin32 ? -$$ : -getpgrp);
252             }
253             }
254             }
255             }
256              
257             ## Child processes.
258             elsif ($_is_sig) {
259              
260             ## Windows support, from nested workers.
261 0 0       0 if ($_is_MSWin32) {
262 0 0       0 _remove_tmpdir() if defined($tmp_dir);
263 0         0 CORE::kill('KILL', $main_proc_id, -$$);
264             }
265              
266             ## Real child processes.
267             else {
268 0         0 CORE::kill($_sig_name, $main_proc_id, -$$);
269 0         0 CORE::kill('KILL', -$$, $$);
270             }
271             }
272              
273             ## Exit with status.
274 22         980 CORE::exit($_exit_status);
275             }
276              
277             ###############################################################################
278             ## ----------------------------------------------------------------------------
279             ## Run command via the system(...) function.
280             ##
281             ## The system function in Perl ignores SIGINT and SIGQUIT. These 2 signals
282             ## are sent to the command being executed via system() but not back to
283             ## the underlying Perl script. The code below will ensure the Perl script
284             ## receives the same signal in order to raise an exception immediately
285             ## after the system call.
286             ##
287             ## Returns the actual exit status.
288             ##
289             ###############################################################################
290              
291             sub sys_cmd {
292 0 0 0 0 1 0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
293              
294 0 0       0 _croak('MCE::Signal::sys_cmd: no arguments were specified') if (@_ == 0);
295              
296 0         0 my $_status = system(@_);
297 0         0 my $_sig_no = $_status & 127;
298 0         0 my $_exit_status = $_status >> 8;
299              
300             ## Kill the process group if command caught SIGINT or SIGQUIT.
301              
302 0 0       0 CORE::kill('INT', $main_proc_id, $_is_MSWin32 ? -$$ : -getpgrp)
    0          
303             if $_sig_no == 2;
304              
305 0 0       0 CORE::kill('QUIT', $main_proc_id, $_is_MSWin32 ? -$$ : -getpgrp)
    0          
306             if $_sig_no == 3;
307              
308 0         0 return $_exit_status;
309             }
310              
311             ###############################################################################
312             ## ----------------------------------------------------------------------------
313             ## Signal handlers for __DIE__ & __WARN__ utilized by MCE.
314             ##
315             ###############################################################################
316              
317             sub _die_handler {
318 0 0 0 0   0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
319              
320 0 0 0     0 if (!defined $^S || $^S) {
321 0 0 0     0 if ( ($INC{'threads.pm'} && threads->tid() != 0) ||
      0        
322             $ENV{'PERL_IPERL_RUNNING'}
323             ) {
324             # thread env or running inside IPerl, check stack trace
325 0         0 my $_t = Carp::longmess(); $_t =~ s/\teval [^\n]+\n$//;
  0         0  
326 0 0 0     0 if ( $_t =~ /^(?:[^\n]+\n){1,7}\teval / ||
327             $_t =~ /\n\teval [^\n]+\n\t(?:eval|Try)/ )
328             {
329 0         0 CORE::die(@_);
330             }
331             }
332             else {
333             # normal env, trust $^S
334 0         0 CORE::die(@_);
335             }
336             }
337              
338 0         0 local $\ = undef;
339              
340             ## Set $MCE::Signal::display_die_with_localtime = 1;
341             ## when wanting the output to contain the localtime.
342              
343 0 0       0 if (defined $_[0]) {
344 0         0 my $mesg = $_[0]; $mesg =~ s/, <__ANONIO__> line \d+//;
  0         0  
345 0 0       0 if ($MCE::Signal::display_die_with_localtime) {
346 0         0 my $_time_stamp = localtime;
347 0         0 print {*STDERR} "## $_time_stamp: $prog_name: ERROR:\n", $mesg;
  0         0  
348             }
349             else {
350 0         0 print {*STDERR} $mesg;
  0         0  
351             }
352             }
353              
354 0         0 MCE::Signal::stop_and_exit('__DIE__');
355             }
356              
357             sub _warn_handler {
358 0 0 0 0   0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
359              
360             ## Ignore thread warnings during exiting.
361              
362             return if (
363 0 0 0     0 $_[0] =~ /^Finished with active (?:child|hobo) processes/ ||
      0        
      0        
      0        
364             $_[0] =~ /^A thread exited while \d+ threads were running/ ||
365             $_[0] =~ /^Attempt to free unreferenced scalar/ ||
366             $_[0] =~ /^Perl exited with active threads/ ||
367             $_[0] =~ /^Thread \d+ terminated abnormally/
368             );
369              
370 0         0 local $\ = undef;
371              
372             ## Set $MCE::Signal::display_warn_with_localtime = 1;
373             ## when wanting the output to contain the localtime.
374              
375 0 0       0 if (defined $_[0]) {
376 0         0 my $mesg = $_[0]; $mesg =~ s/, <__ANONIO__> line \d+//;
  0         0  
377 0 0       0 if ($MCE::Signal::display_warn_with_localtime) {
378 0         0 my $_time_stamp = localtime;
379 0         0 print {*STDERR} "## $_time_stamp: $prog_name: WARNING:\n", $mesg;
  0         0  
380             }
381             else {
382 0         0 print {*STDERR} $mesg;
  0         0  
383             }
384             }
385              
386 0         0 return;
387             }
388              
389             1;
390              
391             ###############################################################################
392             ## ----------------------------------------------------------------------------
393             ## TIE scalar package for making $MCE::Signal::tmp_dir on demand.
394             ##
395             ###############################################################################
396              
397             package MCE::Signal::_tmpdir;
398              
399             sub TIESCALAR {
400 97     97   231 my $_class = shift;
401 97 50       176 bless \do{ my $o = defined $_[0] ? shift : undef }, $_class;
  97         799  
402             }
403              
404             sub STORE {
405 43     43   370 ${ $_[0] } = $_[1];
  43         347  
406              
407 43 100       467 $_safe_clean = 0 if ( length $_[1] < 9 );
408 43 50 33     392 $_safe_clean = 0 if ( $ENV{'TEMP'} && $ENV{'TEMP'} eq $_[1] );
409 43 50       269 $_safe_clean = 0 if ( $_[1] =~ m{[\\/](?:etc|bin|lib|sbin)} );
410 43 50       226 $_safe_clean = 0 if ( $_[1] =~ m{[\\/](?:temp|tmp)[\\/]?$}i );
411              
412 43         168 $_[1];
413             }
414              
415             sub FETCH {
416 805 100   805   1947 if (!defined ${ $_[0] }) {
  805         3505  
417 595         1500 my $_caller = caller();
418 595 50 66     2507 if ($_caller ne 'MCE' && $_caller ne 'MCE::Signal') {
419 0 0 0     0 if ($INC{'MCE.pm'} && MCE->wid() > 0) {
420 0         0 ${ $_[0] } = MCE->tmp_dir();
  0         0  
421             } else {
422 0         0 ${ $_[0] } = MCE::Signal::_make_tmpdir();
  0         0  
423             }
424             }
425             }
426 805         1306 ${ $_[0] };
  805         11482  
427             }
428              
429             1;
430              
431             __END__