File Coverage

blib/lib/MCE/Signal.pm
Criterion Covered Total %
statement 89 173 51.4
branch 43 146 29.4
condition 27 122 22.1
subroutine 17 25 68.0
pod 3 3 100.0
total 179 469 38.1


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   282500 use strict;
  97         208  
  97         3934  
10 97     97   516 use warnings;
  97         181  
  97         2919  
11              
12 97     97   472 no warnings qw( threads recursion uninitialized once );
  97         183  
  97         8870  
13              
14             our $VERSION = '1.889';
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   652 use Carp ();
  97         192  
  97         10074  
24              
25             BEGIN {
26 97     97   488 $main_proc_id = $$;
27 97         239 $prog_name = $0;
28 97         630 $prog_name =~ s{^.*[\\/]}{}g;
29 97 50 33     1005 $prog_name = 'perl' if ($prog_name eq '-e' || $prog_name eq '-');
30              
31 97         3044 return;
32             }
33              
34 97     97   588 use base qw( Exporter );
  97         232  
  97         11346  
35 97     97   3257 use Time::HiRes ();
  97         7337  
  97         113150  
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   5625 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   45 my $_class = shift;
66 4 50       16 return if $_imported++;
67              
68 4         11 my ($_no_setpgrp, $_no_sigmsg, $_setpgrp, @_export_args) = (0, 0, 0);
69              
70 4         12 while (my $_arg = shift) {
71 7 50 0     21 $_setpgrp = _flag() and next if ($_arg eq '-setpgrp');
72 7 50 0     24 $_keep_tmp_dir = _flag() and next if ($_arg eq '-keep_tmp_dir');
73 7 100 50     17 $_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     14 $_no_sigmsg = _flag() and next if ($_arg eq '-no_sigmsg');
79              
80 6 50       32 _usage($_arg) if ($_arg =~ /^-/);
81              
82 6         94 push @_export_args, $_arg;
83             }
84              
85 4         9 local $Exporter::ExportLevel = 1;
86 4         431 Exporter::import($_class, @_export_args);
87              
88             ## Sets the current process group for the current process.
89 4 50 33     22 setpgrp(0,0) if ($_setpgrp == 1 && !$_is_MSWin32);
90              
91             ## Make tmp_dir if caller requested it.
92 4 100 100     33 _make_tmpdir() if ($_use_dev_shm || grep /tmp_dir/, @_export_args);
93              
94 4         60 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   137 my ($_count, $_tmp_base_dir) = (0);
120              
121 34 0 33     231 return $tmp_dir if (defined $tmp_dir && -d $tmp_dir && -w _);
      33        
122              
123 34 50 33     1101 if ($ENV{TEMP} && -d $ENV{TEMP} && -w _) {
    50 33        
      33        
      33        
      0        
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             elsif (! -w '/tmp' && -e $ENV{TMPDIR} && -d $ENV{TMPDIR} && -w _) {
132 0         0 $_tmp_base_dir = $ENV{TMPDIR};
133             }
134             else {
135 34 100 66     254 $_tmp_base_dir = ($_use_dev_shm && -d '/dev/shm' && -w _)
136             ? '/dev/shm' : '/tmp';
137             }
138              
139             _croak("Error: MCE::Signal: ($_tmp_base_dir) is not writeable")
140 34 50 33     578 if (! exists $ENV{'MOBASTARTUPDIR'} && ! -w $_tmp_base_dir);
141              
142             ## Remove tainted'ness from $tmp_dir.
143 34         585 ($tmp_dir) = "$_tmp_base_dir/$prog_name.$$.$_count" =~ /(.*)/;
144              
145 34         146 while ( !(mkdir $tmp_dir, 0770) ) {
146 0         0 ($tmp_dir) = ("$_tmp_base_dir/$prog_name.$$.".(++$_count)) =~ /(.*)/;
147             }
148              
149 34         219 $_safe_clean = 1;
150              
151 34         132 return $tmp_dir;
152             }
153              
154             sub _remove_tmpdir {
155 9 50 33 9   33 return if (!defined $tmp_dir || $tmp_dir eq '' || ! -d $tmp_dir);
      33        
156              
157 9 50       77 if ($_keep_tmp_dir == 1) {
    50          
158 0         0 print {*STDERR} "$prog_name: saved tmp_dir = $tmp_dir\n";
  0         0  
159             }
160             elsif ($_safe_clean) {
161 9 50 33     75 if ($ENV{'TEMP'} && $^O =~ /mswin|mingw|msys|cygwin/i) {
162             ## remove tainted'ness
163 0         0 my ($_dir) = $ENV{'TEMP'} =~ /(.*)/;
164 0 0       0 chdir $_dir if -d $_dir;
165             }
166 9         39 rmdir $tmp_dir;
167 9 50       73 if (-d $tmp_dir) {
168 0         0 local $@; local $SIG{__DIE__};
  0         0  
169 0         0 eval 'require File::Path; File::Path::rmtree($tmp_dir)';
170             }
171             }
172              
173 9         88 $tmp_dir = undef;
174             }
175              
176             ###############################################################################
177             ## ----------------------------------------------------------------------------
178             ## Stops execution, removes temp directory and exits cleanly.
179             ##
180             ## Provides safe reentrant logic for parent and child processes.
181             ## The $main_proc_id variable is defined above.
182             ##
183             ###############################################################################
184              
185             BEGIN {
186 97     97   423 $MCE::Signal::IPC = 0; # 1 = defer signal_handling until completed IPC
187 97         171202 $MCE::Signal::SIG = ''; # signal received during IPC in MCE::Shared 1.863
188             }
189              
190             sub defer {
191 0 0   0 1 0 $MCE::Signal::SIG = $_[0] if $_[0];
192 0         0 return;
193             }
194              
195             my %_sig_name_lkup = map { $_ => 1 } qw(
196             __DIE__ ABRT HUP INT PIPE QUIT TERM __WARN__
197             );
198              
199             my $_count = 0;
200              
201             my $_handler_count = $INC{'threads/shared.pm'}
202             ? threads::shared::share($_count)
203             : \$_count;
204              
205             sub stop_and_exit {
206 22 50 33 22 1 323 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
207 22 50       178 return MCE::Signal::defer($_[0]) if $MCE::Signal::IPC;
208              
209 22   50     213 my ($_exit_status, $_is_sig, $_sig_name) = ($?, 0, $_[0] || 0);
210 22     0   353 $SIG{__DIE__} = $SIG{__WARN__} = sub {};
211              
212 22 50       140 if (exists $_sig_name_lkup{$_sig_name}) {
213 0         0 $_exit_status = $MCE::Signal::KILLED = $_is_sig = 1;
214 0 0       0 $_exit_status = 255, $_sig_name = 'TERM' if ($_sig_name eq '__DIE__');
215 0 0       0 $_exit_status = 0 if ($_sig_name eq 'PIPE');
216 0     0   0 $SIG{INT} = $SIG{$_sig_name} = sub {};
217             }
218             else {
219 22 50       339 $_exit_status = $_sig_name if ($_sig_name =~ /^\d+$/);
220 22         93 $MCE::Signal::STOPPED = 1;
221             }
222              
223             ## Main process.
224 22 50       179 if ($$ == $main_proc_id) {
    0          
225              
226 22 50       50 if (++${ $_handler_count } == 1) {
  22         150  
227             ## Kill process group if signaled.
228 22 50       125 if ($_is_sig == 1) {
229 0 0       0 ($_sig_name eq 'PIPE')
    0          
    0          
230             ? CORE::kill('PIPE', $_is_MSWin32 ? -$$ : -getpgrp)
231             : CORE::kill('INT' , $_is_MSWin32 ? -$$ : -getpgrp);
232              
233 0 0       0 if ($_sig_name eq 'PIPE') {
234 0         0 for my $_i (1..2) { Time::HiRes::sleep(0.015); }
  0         0  
235             } else {
236 0         0 for my $_i (1..3) { Time::HiRes::sleep(0.060); }
  0         0  
237             }
238             }
239              
240             ## Remove temp directory.
241 22 100       349 _remove_tmpdir() if defined($tmp_dir);
242              
243             ## Signal process group to die.
244 22 50       212 if ($_is_sig == 1) {
245 0 0 0     0 if ($_sig_name eq 'INT' && -t STDIN) { ## no critic
246 0         0 print {*STDERR} "\n";
  0         0  
247             }
248 0 0 0     0 if ($INC{'threads.pm'} && ($] lt '5.012000' || threads->tid())) {
      0        
249 0 0 0     0 ($_no_kill9 == 1 || $_sig_name eq 'PIPE')
    0          
250             ? CORE::kill('INT', $_is_MSWin32 ? -$$ : -getpgrp)
251             : CORE::kill('KILL', -$$);
252             }
253             else {
254 0 0       0 CORE::kill('INT', $_is_MSWin32 ? -$$ : -getpgrp);
255             }
256             }
257             }
258             }
259              
260             ## Child processes.
261             elsif ($_is_sig) {
262              
263             ## Windows support, from nested workers.
264 0 0       0 if ($_is_MSWin32) {
265 0 0       0 _remove_tmpdir() if defined($tmp_dir);
266 0         0 CORE::kill('KILL', $main_proc_id, -$$);
267             }
268              
269             ## Real child processes.
270             else {
271 0         0 CORE::kill($_sig_name, $main_proc_id, -$$);
272 0         0 CORE::kill('KILL', -$$, $$);
273             }
274             }
275              
276             ## Exit with status.
277 22         777 CORE::exit($_exit_status);
278             }
279              
280             ###############################################################################
281             ## ----------------------------------------------------------------------------
282             ## Run command via the system(...) function.
283             ##
284             ## The system function in Perl ignores SIGINT and SIGQUIT. These 2 signals
285             ## are sent to the command being executed via system() but not back to
286             ## the underlying Perl script. The code below will ensure the Perl script
287             ## receives the same signal in order to raise an exception immediately
288             ## after the system call.
289             ##
290             ## Returns the actual exit status.
291             ##
292             ###############################################################################
293              
294             sub sys_cmd {
295 0 0 0 0 1 0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
296              
297 0 0       0 _croak('MCE::Signal::sys_cmd: no arguments were specified') if (@_ == 0);
298              
299 0         0 my $_status = system(@_);
300 0         0 my $_sig_no = $_status & 127;
301 0         0 my $_exit_status = $_status >> 8;
302              
303             ## Kill the process group if command caught SIGINT or SIGQUIT.
304              
305 0 0       0 CORE::kill('INT', $main_proc_id, $_is_MSWin32 ? -$$ : -getpgrp)
    0          
306             if $_sig_no == 2;
307              
308 0 0       0 CORE::kill('QUIT', $main_proc_id, $_is_MSWin32 ? -$$ : -getpgrp)
    0          
309             if $_sig_no == 3;
310              
311 0         0 return $_exit_status;
312             }
313              
314             ###############################################################################
315             ## ----------------------------------------------------------------------------
316             ## Signal handlers for __DIE__ & __WARN__ utilized by MCE.
317             ##
318             ###############################################################################
319              
320             sub _die_handler {
321 0 0 0 0   0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
322              
323 0 0 0     0 if (!defined $^S || $^S) {
324 0 0 0     0 if ( ($INC{'threads.pm'} && threads->tid() != 0) ||
      0        
325             $ENV{'PERL_IPERL_RUNNING'}
326             ) {
327             # thread env or running inside IPerl, check stack trace
328 0         0 my $_t = Carp::longmess(); $_t =~ s/\teval [^\n]+\n$//;
  0         0  
329 0 0 0     0 if ( $_t =~ /^(?:[^\n]+\n){1,7}\teval / ||
330             $_t =~ /\n\teval [^\n]+\n\t(?:eval|Try)/ )
331             {
332 0         0 CORE::die(@_);
333             }
334             }
335             else {
336             # normal env, trust $^S
337 0         0 CORE::die(@_);
338             }
339             }
340              
341 0         0 local $\ = undef;
342              
343             ## Set $MCE::Signal::display_die_with_localtime = 1;
344             ## when wanting the output to contain the localtime.
345              
346 0 0       0 if (defined $_[0]) {
347 0         0 my $mesg = $_[0]; $mesg =~ s/, <__ANONIO__> line \d+//;
  0         0  
348 0 0       0 if ($MCE::Signal::display_die_with_localtime) {
349 0         0 my $_time_stamp = localtime;
350 0         0 print {*STDERR} "## $_time_stamp: $prog_name: ERROR:\n", $mesg;
  0         0  
351             }
352             else {
353 0         0 print {*STDERR} $mesg;
  0         0  
354             }
355             }
356              
357 0         0 MCE::Signal::stop_and_exit('__DIE__');
358             }
359              
360             sub _warn_handler {
361 0 0 0 0   0 shift @_ if (defined $_[0] && $_[0] eq 'MCE::Signal');
362              
363             ## Ignore thread warnings during exiting.
364              
365             return if (
366 0 0 0     0 $_[0] =~ /^Finished with active (?:child|hobo) processes/ ||
      0        
      0        
      0        
367             $_[0] =~ /^A thread exited while \d+ threads were running/ ||
368             $_[0] =~ /^Attempt to free unreferenced scalar/ ||
369             $_[0] =~ /^Perl exited with active threads/ ||
370             $_[0] =~ /^Thread \d+ terminated abnormally/
371             );
372              
373 0         0 local $\ = undef;
374              
375             ## Set $MCE::Signal::display_warn_with_localtime = 1;
376             ## when wanting the output to contain the localtime.
377              
378 0 0       0 if (defined $_[0]) {
379 0         0 my $mesg = $_[0]; $mesg =~ s/, <__ANONIO__> line \d+//;
  0         0  
380 0 0       0 if ($MCE::Signal::display_warn_with_localtime) {
381 0         0 my $_time_stamp = localtime;
382 0         0 print {*STDERR} "## $_time_stamp: $prog_name: WARNING:\n", $mesg;
  0         0  
383             }
384             else {
385 0         0 print {*STDERR} $mesg;
  0         0  
386             }
387             }
388              
389 0         0 return;
390             }
391              
392             1;
393              
394             ###############################################################################
395             ## ----------------------------------------------------------------------------
396             ## TIE scalar package for making $MCE::Signal::tmp_dir on demand.
397             ##
398             ###############################################################################
399              
400             package MCE::Signal::_tmpdir;
401              
402             sub TIESCALAR {
403 97     97   240 my $_class = shift;
404 97 50       152 bless \do{ my $o = defined $_[0] ? shift : undef }, $_class;
  97         650  
405             }
406              
407             sub STORE {
408 43     43   371 ${ $_[0] } = $_[1];
  43         295  
409              
410 43 100       417 $_safe_clean = 0 if ( length $_[1] < 9 );
411 43 50 33     348 $_safe_clean = 0 if ( $ENV{'TEMP'} && $ENV{'TEMP'} eq $_[1] );
412 43 50       249 $_safe_clean = 0 if ( $_[1] =~ m{[\\/](?:etc|bin|lib|sbin)} );
413 43 50       190 $_safe_clean = 0 if ( $_[1] =~ m{[\\/](?:temp|tmp)[\\/]?$}i );
414              
415 43         145 $_[1];
416             }
417              
418             sub FETCH {
419 805 100   805   1764 if (!defined ${ $_[0] }) {
  805         3096  
420 595         1350 my $_caller = caller();
421 595 50 66     2328 if ($_caller ne 'MCE' && $_caller ne 'MCE::Signal') {
422 0 0 0     0 if ($INC{'MCE.pm'} && MCE->wid() > 0) {
423 0         0 ${ $_[0] } = MCE->tmp_dir();
  0         0  
424             } else {
425 0         0 ${ $_[0] } = MCE::Signal::_make_tmpdir();
  0         0  
426             }
427             }
428             }
429 805         1276 ${ $_[0] };
  805         10329  
430             }
431              
432             1;
433              
434             __END__