File Coverage

lib/Mojo/IOLoop/ReadWriteProcess/Session.pm
Criterion Covered Total %
statement 109 117 93.1
branch 27 58 46.5
condition 11 26 42.3
subroutine 37 37 100.0
pod 16 18 88.8
total 200 256 78.1


line stmt bran cond sub pod time code
1             package Mojo::IOLoop::ReadWriteProcess::Session;
2              
3 38     38   248 use Mojo::Base 'Mojo::EventEmitter';
  38         64  
  38         239  
4 38     38   5675 use Mojo::IOLoop::ReadWriteProcess;
  38         95  
  38         322  
5 38     38   1333 use Carp 'confess';
  38         64  
  38         2159  
6 38     38   237 use POSIX qw( :sys_wait_h :signal_h );
  38         171  
  38         339  
7 38     38   14892 use Mojo::Collection 'c';
  38         158  
  38         3455  
8              
9             our @EXPORT_OK = qw(session);
10 38     38   266 use Exporter 'import';
  38         72  
  38         1179  
11              
12 38     38   199 use Config;
  38         82  
  38         1961  
13              
14 38     38   546 use constant DEBUG => $ENV{MOJO_PROCESS_DEBUG};
  38         82  
  38         2729  
15              
16             # See https://github.com/torvalds/linux/blob/master/include/uapi/linux/prctl.h
17 38     38   253 use constant PR_SET_CHILD_SUBREAPER => 36;
  38         98  
  38         2005  
18 38     38   227 use constant PR_GET_CHILD_SUBREAPER => 37;
  38         61  
  38         85559  
19              
20             has subreaper => 0;
21             has collect_status => 1;
22             has orphans => sub { {} };
23             has process_table => sub { {} };
24             has collected_info => sub { [] };
25             has 'handler';
26             has emit_from_sigchld => 1;
27              
28             my $singleton;
29              
30 426   66 426 1 46104 sub new { $singleton ||= __PACKAGE__->SUPER::new }
31              
32             sub disable {
33 1     1 1 4 $singleton->_protect(sub { $SIG{CHLD} = $singleton->handler() });
  1     1   8  
34             }
35              
36             sub _protect {
37 730 100 66 730   13921 shift if $_[0] && $_[0] eq $singleton;
38 730 100       2745 my ($sig, $cb) = (@_ > 1 ? pop : SIGCHLD, pop);
39 730         8725 my ($sigset, $blockset) = (POSIX::SigSet->new, POSIX::SigSet->new($sig));
40 730         3982 $singleton->emit(protect => [$cb, $sig]);
41 730         13163 sigprocmask(SIG_BLOCK, $blockset, $sigset);
42 730         2644 my $r = $cb->();
43 730         9376 sigprocmask(SIG_SETMASK, $sigset);
44 730         4969 return $r;
45             }
46              
47             sub enable {
48 274 100   274 1 2323 return if $singleton->handler();
49 83         797 $singleton->handler($SIG{CHLD});
50             $singleton->_protect(
51             sub {
52             $SIG{CHLD} = sub {
53 251         34537428 local ($!, $?);
54 251         3152 $singleton->emit('SIG_CHLD');
55 251 100       7880 return unless $singleton->collect_status;
56 249         13534 while ((my $pid = waitpid(-1, WNOHANG)) > 0) {
57 271         3927 $singleton->add_collected_info($pid, $?, $!);
58             }
59             $singleton->consume_collected_info()
60 249 100       6464 if ($singleton->emit_from_sigchld());
61             }
62 83     83   1779 });
  83         1637  
63             }
64              
65             sub _collect {
66 222     222   1420 my ($self, $pid, $status, $errno) = @_;
67 222         1010 my $p = $singleton->resolve($pid);
68 222         1271 $p->emit('SIG_CHLD')->emit(collect_status => $pid => $status => $errno)
69             ->emit('collected')->emit('stop');
70             }
71              
72             sub collect {
73 271     271 1 1463 my ($self, $pid, $status, $errno) = @_;
74 271 100       1473 if ($singleton->resolve($pid)) {
75 222         1383 $singleton->_collect($pid => $status => $errno);
76 222         9877 $singleton->emit(collected => $singleton->resolve($pid));
77             }
78             else {
79 49         507 $singleton->orphans->{$pid}
80             = Mojo::IOLoop::ReadWriteProcess->new(process_id => $pid)
81             ->_fork_collect_status($pid => $status => $errno);
82 49         897 $singleton->emit(collected_orphan => $singleton->orphan($pid));
83             }
84 271         3797 return $singleton;
85             }
86              
87             sub consume_collected_info {
88 2323     2323 0 22148 while (my $i = shift @{$singleton->collected_info}) {
  2594         8552  
89 271         4373 $singleton->collect(@$i);
90             }
91             }
92              
93             sub add_collected_info {
94 271     271 0 679 shift;
95 271         585 push @{$singleton->collected_info}, [@_];
  271         1746  
96             }
97              
98             # Use as $pid => Mojo::IOLoop::ReadWriteProcess
99             sub register {
100 309     309 1 10645 my ($process, $pid) = (pop, pop);
101 309         5686 $singleton->process_table()->{$pid} = \$process;
102 309         6160 $singleton->emit(register => $process);
103             }
104              
105 1     1 1 5 sub unregister { delete($singleton->process_table()->{+pop()}) }
106              
107             sub _resolve {
108 770     770   3270 my ($el, $w) = (pop, pop);
109             return
110             exists $singleton->{$w}->{$el}
111             ? $w eq 'orphans'
112             ? $singleton->{$w}->{$el}
113 770 100       3624 : ${$singleton->{$w}->{$el}}
  671 100       2683  
114             : undef;
115             }
116 49     49 1 177 sub orphan { _resolve(orphans => pop()) }
117 721     721 1 2663 sub resolve { _resolve(process_table => pop()) }
118              
119             sub clean {
120             $_[0]->resolve($_)->stop() and $_[0]->resolve($_)->DESTROY()
121 2   33 2 1 7 for keys %{$_[0]->process_table()};
  2         8  
122             $_[0]->orphan($_)->stop() and $_[0]->orphan($_)->DESTROY()
123 2   0     8 for keys %{$_[0]->orphans()};
  2         7  
124 2         9 shift->reset();
125             }
126              
127 43     43 1 10744 sub all { c($singleton->all_processes, $singleton->all_orphans)->flatten }
128 55     55 1 12603 sub all_orphans { c(values %{$singleton->orphans}) }
  55         196  
129              
130             sub all_processes {
131 171     171 1 2133 c(values %{$singleton->process_table})->map(sub { ${$_} });
  171     53   434  
  53         18648  
  53         188  
132             }
133              
134             sub contains {
135 30     30 1 17740 my $pid = pop;
136 30     90   190 $singleton->all->grep(sub { $_->pid eq $pid })->size == 1;
  90         2860  
137             }
138              
139             sub reset {
140 55         1700 @{+shift}
141 55     55 1 108053 {qw(events orphans process_table collected_info handler emit_from_sigchld)}
142             = ({}, {}, {}, [], undef, 1);
143             }
144              
145             # XXX: This should be replaced by PR_GET_CHILD_SUBREAPER
146             sub disable_subreaper {
147 7 50   7 1 4689 $singleton->subreaper(
148             $singleton->_prctl(PR_SET_CHILD_SUBREAPER, 0) == 0 ? 0 : 1);
149             }
150              
151             sub enable_subreaper {
152 47 50   47 1 943 $singleton->subreaper(
153             $singleton->_prctl(PR_SET_CHILD_SUBREAPER, 1) == 0 ? 1 : 0);
154             }
155              
156             sub _get_prctl_syscall {
157              
158             # Courtesy of Sys::Prctl
159 122 50   122   889 confess "Only Linux is supported" unless $^O eq 'linux';
160              
161 122         1398 my $machine = (POSIX::uname())[4];
162 122 50       493 die "Could not get machine type" unless $machine;
163              
164             # if we're running on an x86_64 kernel, but a 32-bit process,
165             # we need to use the i386 syscall numbers.
166 122 50 33     4198 $machine = "i386" if ($machine eq "x86_64" && $Config{ptrsize} == 4);
167              
168 122 0 0     1300 my $prctl_call
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
169             = $machine
170             =~ /^i[3456]86|^blackfin|cris|frv|h8300|m32r|m68k|microblaze|mn10300|sh|s390|parisc$/
171             ? 172
172             : $machine eq "x86_64" ? 157
173             : $machine eq "sparc64" ? 147
174             : $machine eq "aarch64" ? 167
175             : ($machine eq "ppc" || $machine eq "ppc64le") ? 171
176             : $machine eq "ia64" ? 1170
177             : $machine eq "alpha" ? 348
178             : $machine eq "avr32" ? 148
179             : $machine eq "mips" ? 4000 + 192
180             : $machine eq "mips64" ? 5000 + 153
181             : $machine eq "xtensa" ? 130
182             : undef;
183              
184 122 50       367 unless (defined $prctl_call) {
185             delete @INC{
186 0         0 qw
187             sys/syscall.ph>
188             };
189 0         0 my $rv = eval { require 'syscall.ph'; 1 } ## no critic
  0         0  
190 0 0       0 or eval { require 'sys/syscall.ph'; 1 }; ## no critic
  0         0  
  0         0  
191              
192 0         0 $prctl_call = eval { &SYS_prctl; };
  0         0  
193             }
194 122         2115 return $prctl_call;
195             }
196              
197             sub _prctl {
198 61     61   1443 my ($self, $option, $arg2, $arg3, $arg4, $arg5) = @_;
199 61 50       221 confess 'prctl not supported in this platform!'
200             unless defined _get_prctl_syscall;
201 61         330 local $!;
202 61   100     195 my $ret = syscall(
      50        
      50        
      50        
203             _get_prctl_syscall(), $option,
204             ($arg2 or 0),
205             ($arg3 or 0),
206             ($arg4 or 0),
207             ($arg5 or 0));
208              
209 61 50       1103 warn "prctl($option) is unavailable on this platform." if $!{EINVAL};
210 61 50       1220 warn "Error! $!" if $!;
211 61         441 return $ret;
212             }
213              
214             *singleton = \&new;
215             *session = \&new;
216             *protect = \&_protect;
217              
218             1;
219              
220             =encoding utf-8
221              
222             =head1 NAME
223              
224             Mojo::IOLoop::ReadWriteProcess::Session - Session manager for handling child processes.
225              
226             =head1 SYNOPSIS
227              
228             use Mojo::IOLoop::ReadWriteProcess::Session;
229             use Mojo::IOLoop::ReadWriteProcess qw(process);
230              
231             my $session = process()->session; # or Mojo::IOLoop::ReadWriteProcess::Session->singleton
232              
233             $session->enable; # Modifies your SIG_CHLD
234              
235             $session->on(collected => sub { warn "Process ".(shift->pid)." collected! "});
236             $session->on(collected_orphan => sub { warn "Orphan process collected! "});
237              
238             $session->enable_subreaper(); # Mark the current process as subreaper
239             $session->disable_subreaper(); # Disable subreaper
240              
241             $session->reset(); # Resets events and clear the process tables
242             $session->clean(); # Stop all processes that result as running and reset
243              
244              
245             =head1 DESCRIPTION
246              
247             Mojo::IOLoop::ReadWriteProcess::Session is a session manager for the collected processes
248              
249             =head1 EVENTS
250              
251             L inherits all events from L and can emit
252             the following new ones.
253              
254             =head2 SIG_CHLD
255              
256             $session->on(SIG_CHLD => sub {
257             my ($self) = @_;
258             ...
259             });
260              
261             Emitted when we receive SIG_CHLD.
262              
263             =head2 collected
264              
265             $session->on(collected => sub {
266             my ($self, $process) = @_;
267             ...
268             });
269              
270             Emitted when child process is collected and it's return status is available.
271              
272             =head2 protect
273              
274             $session->on(protect => sub {
275             my ($self, $detail) = @_;
276             my ($cb, $signal) = @$detail;
277             ...
278             });
279              
280             Emitted when protected callbacks are fired.
281              
282             =head2 collected_orphan
283              
284             $session->on(collected_orphan => sub {
285             my ($self, $process) = @_;
286             $process->pid;
287             $process->exit_status;
288             ...
289             });
290              
291             Emitted when child process is collected and it's exit status is available.
292             Note: here are collected processes that weren't created with L.
293              
294             =head2 register
295              
296             $session->on(register => sub {
297             my ($self, $process) = @_;
298             $process->pid;
299             $process->exit_status;
300             ...
301             });
302              
303             Emitted when a process is registering to a session.
304              
305             =head1 ATTRIBUTES
306              
307             L inherits all attributes from L and implements
308             the following new ones.
309              
310              
311             =head2 subreaper
312              
313             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
314             session->enable_subreaper;
315             my $process = Mojo::IOLoop::ReadWriteProcess->new(code => sub { print "Hello ".$_[1] }, args => "User" );
316             $process->start();
317             $process->on( stop => sub { shift()->disable_subreaper } );
318             $process->stop();
319              
320             # The process will print "Hello User"
321              
322             Mark the current process (not the child) as subreaper on start.
323             It's on invoker behalf to disable subreaper when process stops, as it marks the current process and not the
324             child.
325              
326             =head2 collect_status
327              
328             Defaults to C<1>, If enabled it will automatically collect the status of the children process.
329             Disable it in case you want to manage your process child directly, and do not want to rely on
330             automatic collect status. If you won't overwrite your C handler,
331             the C event will be still emitted.
332              
333             =head2 handler()
334              
335             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
336             session->handler(sub {});
337              
338             Default handler for SIG_CHLD processing, used when C is invoked.
339              
340             =head1 METHODS
341              
342             L inherits all methods from L and implements
343             the following new ones.
344              
345             =head2 enable()
346              
347             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
348             session->enable();
349              
350             Sets the SIG_CHLD handler.
351              
352             =head2 disable()
353              
354             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
355             session->disable();
356              
357             Disables the SIG_CHLD handler and reset with the previous one.
358              
359             =head2 enable_subreaper()
360              
361             use Mojo::IOLoop::ReadWriteProcess qw(process);
362             my $p = process()->enable_subreaper;
363             # or
364             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
365             session->enable_subreaper;
366              
367             Mark the current process (not the child) as subreaper.
368             This is used typically if you want to mark further children as subreapers inside other forks.
369              
370             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
371              
372             my $master_p = process(
373             sub {
374             my $p = shift;
375             $p->enable_subreaper;
376              
377             process(sub { sleep 4; exit 1 })->start();
378             process(
379             sub {
380             sleep 4;
381             process(sub { sleep 1; })->start();
382             })->start();
383             process(sub { sleep 4; exit 0 })->start();
384             process(sub { sleep 4; die })->start();
385             my $manager
386             = process(sub { sleep 2 })->subreaper(1)->start();
387             sleep 1 for (0 .. 10);
388             $manager->stop;
389             return session->all->size;
390             });
391              
392             $master_p->subreaper(1);
393             $master_p->on(collect_status => sub { $status++ });
394              
395             $master_p->on(stop => sub { shift()->disable_subreaper });
396             $master_p->start();
397             session->all->size();
398             ....
399              
400             =head2 disable_subreaper()
401              
402             use Mojo::IOLoop::ReadWriteProcess qw(process);
403             my $p = process()->disable_subreaper;
404              
405             Unset the current process as subreaper.
406              
407             =head2 prctl()
408              
409             use Mojo::IOLoop::ReadWriteProcess qw(process);
410             my $p = process();
411             $p->prctl($option, $arg2, $arg3, $arg4, $arg5);
412              
413             Internal function to execute and wrap the prctl syscall, accepts the same arguments as prctl.
414              
415             =head2 reset()
416              
417             use Mojo::IOLoop::ReadWriteProcess qw(session);
418             session->reset;
419              
420             Wipe the process tables.
421              
422             =head2 clean()
423              
424             use Mojo::IOLoop::ReadWriteProcess qw(session);
425             session->clean;
426              
427             Wipe the process tables, but before attempt to stop running procesess.
428              
429             =head2 all()
430              
431             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
432             my $collection = session->all;
433             $collection->size;
434              
435             Returns a L of L that belongs to a session.
436              
437             =head2 all_orphans()
438              
439             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
440             my $collection = session->all_orphans;
441             $collection->size;
442              
443             Returns a L of L of orphaned processes that belongs to a session.
444             They are automatically turned into a L, also if processes were created by C.
445              
446             =head2 all_processes()
447              
448             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
449             my $collection = session->all_processes;
450             $collection->size;
451              
452             Returns a L of all L known processes that belongs to a session.
453              
454             =head2 contains()
455              
456             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
457             my $collection = session->contains(13443);
458             $collection->size;
459              
460             Returns true if the pid is contained in any of the process tables.
461              
462             =head2 resolve()
463              
464             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
465             my $process = session->resolve(12233);
466              
467             Returns the L process identified by its pid if belongs to the process table.
468              
469             =head2 orphan()
470              
471             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
472             my $process = session->orphan(12233);
473              
474             Returns the L process identified by its pid if belongs to the process table of unknown processes.
475              
476             =head2 register()
477              
478             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
479             my $process = session->register('pid' => Mojo::IOLoop::ReadWriteProcess->new);
480              
481             Register the L process to the session.
482              
483             =head2 unregister()
484              
485             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
486             my $process = session->unregister(123342);
487              
488             Unregister the corresponding L with the given pid.
489              
490             =head2 collect()
491              
492             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
493             my $process = session->collect(123342 => 0 => undef);
494              
495             Collect the status for the given pid.
496              
497             =head2 protect()
498              
499             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
500             use POSIX;
501              
502             my $return = session->protect(sub { print "Hello World\n" });
503              
504             session->protect(sub { print "Hello World\n" } => SIGTERM);
505              
506             Try to protect the execution of the callback from signal interrupts.
507              
508             =head1 EXPORTS
509              
510             =head2 session()
511              
512             use Mojo::IOLoop::ReadWriteProcess::Session qw(session);
513             session->enable_subreaper;
514              
515             Returns the L singleton.
516              
517             =head1 DEBUGGING
518              
519             You can set the MOJO_EVENTEMITTER_DEBUG environment variable to get some advanced diagnostics information printed to STDERR.
520              
521             MOJO_EVENTEMITTER_DEBUG=1
522              
523             Also, you can set MOJO_PROCESS_DEBUG environment variable to get diagnostics about the process execution.
524              
525             MOJO_PROCESS_DEBUG=1
526              
527             =head1 LICENSE
528              
529             Copyright (C) Ettore Di Giacinto.
530              
531             This library is free software; you can redistribute it and/or modify
532             it under the same terms as Perl itself.
533              
534             =head1 AUTHOR
535              
536             Ettore Di Giacinto Eedigiacinto@suse.comE
537              
538             =cut