File Coverage

blib/lib/IO/Async/Process/GracefulShutdown.pm
Criterion Covered Total %
statement 34 34 100.0
branch 5 6 83.3
condition 4 5 80.0
subroutine 9 9 100.0
pod 4 4 100.0
total 56 58 96.5


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2019 -- leonerd@leonerd.org.uk
5              
6             package IO::Async::Process::GracefulShutdown;
7              
8 3     3   147044 use strict;
  3         26  
  3         87  
9 3     3   16 use warnings;
  3         6  
  3         82  
10 3     3   67 use 5.010; # //
  3         9  
11 3     3   16 use base qw( IO::Async::Process );
  3         5  
  3         1791  
12              
13             our $VERSION = '0.01';
14              
15             =head1 NAME
16              
17             C - controlled shutdown of a process
18              
19             =head1 SYNOPSIS
20              
21             use IO::Async::Process::GracefulShutdown;
22              
23             use IO::Async::Loop;
24             my $loop = IO::Async::Loop;
25              
26             my $process = IO::Async::Process::GracefulShutdown->new(
27             command => [ "my-proc" ],
28             );
29              
30             $loop->add( $process );
31              
32             ...
33              
34             $process->shutdown( "TERM" )->get;
35              
36             =head1 DESCRIPTION
37              
38             This subclass of L adds a method to perform a shutdown of
39             the invoked process by sending a signal. If after some delay the process has
40             not yet exited, it is sent a C instead.
41              
42             =cut
43              
44             sub configure
45             {
46 4     4 1 13488 my $self = shift;
47 4         14 my %params = @_;
48              
49 4 100       18 if (my $on_finish = delete $params{on_finish}) {
50 1         3 $self->{ia__process__gracefulshutdown_wrapped_on_finish} = $on_finish;
51             }
52              
53 4         31 return $self->SUPER::configure(%params);
54             }
55              
56             =head1 METHODS
57              
58             =cut
59              
60             =head2 finish_future
61              
62             $f = $process->finish_future
63              
64             Returns a L that completes when the process finishes. It will yield
65             the exit code from the process.
66              
67             =cut
68              
69             # TODO: Migrate this to regular IO::Async::Process
70             sub finish_future
71             {
72 9     9 1 11131 my $self = shift;
73 9   66     208 return $self->{ia__process__gracefulshutdown_future} //= $self->loop->new_future;
74             }
75              
76             sub on_finish
77             {
78 4     4 1 49145 my $self = shift;
79 4         18 my ( $exitcode ) = @_;
80              
81 4 100       47 if( my $on_finish = $self->{ia__process__gracefulshutdown_wrapped_on_finish}) {
82 1         5 $self->$on_finish( $exitcode );
83             }
84              
85 4         30 $self->finish_future->done($exitcode);
86             }
87              
88             =head2 shutdown
89              
90             $process->shutdown( $signal, %args )->get
91              
92             Requests the process shut down by sending it the given signal (either
93             specified by name or number). If the process does not shut down after a
94             timeout, C is sent instead.
95              
96             The returned future will complete when the process exits.
97              
98             Takes the following named arguments:
99              
100             =over 4
101              
102             =item timeout => NUM
103              
104             Optional. Number of seconds to wait for exit before sending C.
105             Defaults to 10 seconds.
106              
107             =item on_kill => CODE
108              
109             Optional. Callback to invoke if the timeout occurs and C is going to
110             be sent. Intended for printing or logging purposes; this doesn't have any
111             material effect on the process otherwise.
112              
113             $on_kill->( $process )
114              
115             =back
116              
117             =cut
118              
119             sub shutdown
120             {
121 2     2 1 37110 my $self = shift;
122 2         31 my ( $signal, %args ) = @_;
123              
124 2   100     45 my $timeout = $args{timeout} // 10;
125              
126 2         45 my $pid = $self->pid;
127              
128 2         58 $self->debug_printf( "KILL signal=%s", $signal );
129 2         24 $self->kill( $signal );
130              
131             return Future->needs_any(
132             $self->finish_future->then_done(),
133              
134             $self->loop->delay_future( after => $timeout )->then(
135             sub {
136 1 50   1   101259 $args{on_kill}->( $self ) if $args{on_kill};
137              
138 1         18 $self->debug_printf( "KILL signal=KILL" );
139 1         33 $self->kill( "KILL" );
140 1         307 Future->done;
141             })
142 2         294 );
143             }
144              
145             =head1 AUTHOR
146              
147             Paul Evans
148              
149             =cut
150              
151             0x55AA;