File Coverage

blib/lib/Proc/tored.pm
Criterion Covered Total %
statement 15 22 68.1
branch n/a
condition n/a
subroutine 9 16 56.2
pod 13 13 100.0
total 37 51 72.5


line stmt bran cond sub pod time code
1             package Proc::tored;
2             # ABSTRACT: Service management using a pid file and touch files
3             $Proc::tored::VERSION = '0.20';
4 1     1   230427 use strict;
  1         13  
  1         35  
5 1     1   6 use warnings;
  1         3  
  1         55  
6             require Exporter;
7             require Proc::tored::Manager;
8              
9              
10 1     1   342 use parent 'Exporter';
  1         345  
  1         6  
11              
12             our @EXPORT = qw(
13             service
14             in
15             trap
16            
17             pid
18             running
19             zap
20             run
21            
22             stop
23             start
24             stopped
25            
26             pause
27             resume
28             paused
29             );
30              
31 3     3 1 96 sub service ($%) { Proc::tored::Manager->new(name => shift, @_) }
32 4     4 1 32831 sub in ($;@) { dir => shift, @_ }
33 2     2 1 1565 sub trap ($) { trap_signals => shift, @_ }
34              
35 0     0 1 0 sub pid ($) { $_[0]->read_pid }
36 7     7 1 3158 sub running ($) { $_[0]->running_pid }
37 0     0 1 0 sub zap ($;@) { shift->stop_wait(@_) }
38 3     3 1 2203 sub run (&$) { $_[1]->service($_[0]) }
39              
40 1     1 1 25 sub stop ($) { $_[0]->stop }
41 0     0 1   sub start ($) { $_[0]->start }
42 0     0 1   sub stopped ($) { $_[0]->is_stopped }
43              
44 0     0 1   sub pause ($) { $_[0]->pause }
45 0     0 1   sub resume ($) { $_[0]->resume }
46 0     0 1   sub paused ($) { $_[0]->is_paused }
47              
48             1;
49              
50             __END__
51            
52             =pod
53            
54             =encoding UTF-8
55            
56             =head1 NAME
57            
58             Proc::tored - Service management using a pid file and touch files
59            
60             =head1 VERSION
61            
62             version 0.20
63            
64             =head1 SYNOPSIS
65            
66             use Proc::tored;
67             use Getopt::Long;
68            
69             my %opt = (
70             pause => 0,
71             resume => 0,
72             stop => 0,
73             zap => 0,
74             start => 0,
75             run => 0,
76             );
77            
78             GetOptions(
79             'pause' => \$opt{pause},
80             'resume' => \$opt{resume},
81             'stop' => \$opt{stop},
82             'zap' => \$opt{zap},
83             'start' => \$opt{start},
84             'run' => \$opt{run},
85             );
86            
87             my $service = service 'stuff-doer', in '/var/run';
88             my $pid = running $service;
89            
90             print "Running service found with pid $pid\n"
91             if $pid;
92            
93             if ($opt{pause}) {
94             # Set the paused flag, causing a running service to block until unset
95             pause $service;
96             }
97             elsif ($opt{resume}) {
98             # Unset the paused flag, unblocking any running service
99             resume $service;
100             }
101             elsif ($opt{stop}) {
102             # Set the stopped state, preventing new processes from starting the service
103             # and causing running processes to self-terminate
104             stop $service;
105             }
106             elsif ($opt{zap}) {
107             # Terminate a running process, timing out after 15s
108             zap $service, 15
109             or die "stuff_doer $pid is being stubborn";
110             }
111             elsif ($opt{start}) {
112             # Allow the service to start running again
113             start $service;
114             }
115            
116             if ($opt{run}) {
117             # Run service (if not stopped)
118             run { do_stuff() } $service;
119             }
120            
121             =head1 DESCRIPTION
122            
123             A C<Proc::tored> service is voluntarily managed by a pid file and touch files.
124            
125             C<Proc::tored> services are specified with a name and a path. Any services
126             created using the same name and path are considered the same service and will
127             be aware of other processes via their L</PID FILE> and respect service control
128             L</FLAGS>.
129            
130             =head1 EXPORTED SUBROUTINES
131            
132             All routines are exported by default.
133            
134             =head2 service
135            
136             =head2 in
137            
138             =head2 trap
139            
140             A proctored service is defined using the C<service> function. The name given to
141             the service is used in the naming of various files used to control the service
142             (e.g., pid file and touch files). The C<in> function is used to specify the
143             local directory where these files will be created and looked for. Signals may
144             be trapped using C<trap> on non-C<MSWin32> systems.
145            
146             my $service = service 'name-of-service', in '/var/run', trap ['TERM', 'INT'];
147            
148             =head2 pid
149            
150             Reads and returns the contents of the pid file. Does not check to determine
151             whether the pid is valid. Returns 0 if the pid file is not found or is empty.
152            
153             printf "service may be running under pid %d", pid $service;
154            
155             =head2 running
156            
157             Reads and returns the contents of the pid file after checking that the process
158             identified still exists. Essentially the same as C<kill(0, pid $service)>.
159             Returns 0 if the pid is not found or cannot be signalled.
160            
161             if (my $pid = running $service) {
162             warn "service is already running under pid $pid";
163             }
164            
165             =head2 run
166            
167             Begins the service in the current process. The service, specified as a code
168             block, will be called until it returns false or the L</stopped> flag is set.
169            
170             If the L</paused> flag is set, the loop will continue to run without executing
171             the code block until it has been L</resume>d. If the L</paused> flag is set at
172             the time C<run> is called, the loop will start but will not begin executing the
173             code block until the flag is cleared.
174            
175             If the L</stopped> flag is set, the loop will terminate at the completion of
176             the current iteration. If the L</stopped> flag is set at the time C<run> is
177             called, C<run> will return false immediately. The behavior under L</stopped>
178             takes priority over that of L</paused>.
179            
180             my $started = time;
181             my $max_run_time = 300;
182            
183             run {
184             if (time - $started > $max_run_time) {
185             warn "Max run time ($max_run_time seconds) exceeded\n";
186             warn " -shutting down\n";
187             return 0;
188             }
189             else {
190             do_some_work();
191             }
192            
193             return 1;
194             } $service;
195            
196             =head2 zap
197            
198             Sets the "stopped" flag (see L</stop>), then blocks until a running service
199             exits. Returns immediately (after setting the "stopped" flag) if the
200             L</running> service is the current process.
201            
202             sub stop_service {
203             if (my $pid = running $service) {
204             print "Attempting to stop running service running under process $pid\n";
205            
206             if (zap $pid, 30) {
207             print " -Service shut down\n";
208             return 1;
209             }
210             else {
211             print " -Timed out before service shut down\n";
212             return 0;
213             }
214             }
215             }
216            
217             =head2 stop
218            
219             =head2 start
220            
221             =head2 stopped
222            
223             Controls and inspects the "stopped" flag for the service.
224            
225             # Stop a running service
226             if (!stopped $service && running $service) {
227             stop $service;
228             }
229            
230             do_work_while_stopped();
231            
232             # Allow service to start
233             # Note that this does not launch the service process. It simply clears the
234             # "stopped" flag that would have prevented it from running again.
235             start $service;
236            
237             =head2 pause
238            
239             =head2 resume
240            
241             =head2 paused
242            
243             Controls and inspects the "paused" flag for the service. In general, this
244             should never be done inside the L</run> loop (see the warning in L</Pause and
245             resume>).
246            
247             # Pause a running service
248             # Note that the running service will not exit. Instead, it will stop
249             # executing its main loop until the "paused" flag is cleared.
250             if (!paused $service && running $service) {
251             pause $service;
252             }
253            
254             do_work_while_paused();
255            
256             # Allow service to resume execution
257             resume $service;
258            
259             =head1 PID FILE
260            
261             A pid file is used to identify a running service. While the service is running,
262             barring any outside interference, the pid will contain the pid of the running
263             process and a newline. After the service process stops, the pid file will be
264             truncated. The file will be located in the directory specified by L</in>. Its
265             name is the concatenation of the service name and ".pid".
266            
267             =head1 FLAGS
268            
269             Service control flags are persistent until unset. Their status is determined by
270             the existence of a touch file.
271            
272             =head2 stopped
273            
274             A touch file indicating that a running service should self-terminate and that
275             new processes should not start is created with L</stop> and removed with
276             L</start>. It is located in the directory specified by L</in>. Its name is the
277             concatenation of the service name and ".stopped".
278            
279             =head2 paused
280            
281             A touch file indicating that a running service should temporarily stop
282             executing and that new processes should start but not yet execute any service
283             code is created with L</pause> and removed with L</resume>. It is located in
284             the directory specified by L</in>. Its name is the concatenation of the service
285             name and ".paused".
286            
287             =head1 BUGS AND LIMITATIONS
288            
289             =head2 Pause and resume
290            
291             When a service is L</paused>, the code block passed to L</run> is no longer
292             executed until I<something> calls L</resume>. This can lead to deadlock if
293             there is no external actor willing to L</resume> the service.
294            
295             For example, this service will never resume:
296            
297             run {
298             my $empty = out_of_tasks();
299            
300             if ($empty) {
301             pause $service;
302             }
303             elsif (paused $service && !$empty) {
304             # This line is never reached because this code block is no longer
305             # executed after being paused above.
306             resume $service;
307             }
308            
309             do_next_task();
310             return 1;
311             } $service;
312            
313             In most cases, pausing and resuming a service should be handled from outside of
314             L</run>.
315            
316             =head1 AUTHOR
317            
318             Jeff Ober <sysread@fastmail.fm>
319            
320             =head1 COPYRIGHT AND LICENSE
321            
322             This software is copyright (c) 2017 by Jeff Ober.
323            
324             This is free software; you can redistribute it and/or modify it under
325             the same terms as the Perl 5 programming language system itself.
326            
327             =cut
328