File Coverage

blib/lib/Proc/tored/Manager.pm
Criterion Covered Total %
statement 52 61 85.2
branch 0 2 0.0
condition 0 7 0.0
subroutine 18 19 94.7
pod 2 2 100.0
total 72 91 79.1


line stmt bran cond sub pod time code
1             package Proc::tored::Manager;
2             # ABSTRACT: OO interface to creating a proctored service
3             $Proc::tored::Manager::VERSION = '0.20';
4              
5 2     2   248401 use strict;
  2         17  
  2         67  
6 2     2   12 use warnings;
  2         6  
  2         66  
7 2     2   837 use Moo;
  2         22812  
  2         13  
8 2     2   3330 use Carp;
  2         6  
  2         141  
9 2     2   15 use Fcntl qw(:flock :seek :DEFAULT);
  2         6  
  2         1040  
10 2     2   18 use Path::Tiny qw(path);
  2         6  
  2         116  
11 2     2   849 use Time::HiRes qw(sleep);
  2         3081  
  2         12  
12 2     2   1017 use Try::Tiny;
  2         2533  
  2         122  
13 2     2   756 use Types::Standard -all;
  2         188002  
  2         27  
14 2     2   130095 use Proc::tored::Flag;
  2         14  
  2         108  
15 2     2   930 use Proc::tored::Machine;
  2         9  
  2         73  
16 2     2   20 use Proc::tored::PidFile;
  2         5  
  2         79  
17 2     2   15 use Proc::tored::Types -types;
  2         4  
  2         20  
18              
19              
20             has name => (is => 'ro', isa => NonEmptyStr, required => 1);
21             has dir => (is => 'ro', isa => Dir, required => 1);
22             has pid_file => (is => 'lazy', isa => NonEmptyStr);
23             has stop_file => (is => 'lazy', isa => NonEmptyStr);
24             has pause_file => (is => 'lazy', isa => NonEmptyStr);
25             has trap_signals => (is => 'ro', isa => SignalList, default => sub {[]});
26              
27             sub _build_pid_file {
28 5     5   64   my $self = shift;
29 5         41   my $file = path($self->dir)->child($self->name . '.pid');
30 5         481   return "$file";
31             }
32              
33             sub _build_stop_file {
34 5     5   322   my $self = shift;
35 5         64   my $file = path($self->dir)->child($self->name . '.stopped');
36 5         333   return "$file";
37             }
38              
39             sub _build_pause_file {
40 5     5   274   my $self = shift;
41 5         25   my $file = path($self->dir)->child($self->name . '.paused');
42 5         297   return "$file";
43             }
44              
45             has machine => (
46               is => 'lazy',
47               isa => InstanceOf['Proc::tored::Machine'],
48               handles => [qw(
49             clear_flags
50             stop start is_stopped
51             pause resume is_paused
52             read_pid running_pid is_running
53             )],
54             );
55              
56             sub _build_machine {
57 5     5   6185   my $self = shift;
58 5         107   Proc::tored::Machine->new(
59                 pidfile_path => $self->pid_file,
60                 stop_path => $self->stop_file,
61                 pause_path => $self->pause_file,
62                 traps => $self->trap_signals,
63               );
64             }
65              
66              
67             sub stop_wait {
68 0     0 1 0   my ($self, $timeout, $sleep) = @_;
69 0   0     0   $sleep ||= 0.2;
70              
71 0         0   $self->stop;
72 0 0       0   return if $self->is_running;
73              
74 0   0     0   my $pid = $self->running_pid || return 0;
75              
76 0   0     0   while (kill(0, $pid) && $timeout > 0) {
77 0         0     sleep $sleep;
78 0         0     $timeout -= $sleep;
79               }
80              
81 0         0   !kill(0, $pid);
82             }
83              
84              
85             sub service {
86 9     9 1 1154   my ($self, $code) = @_;
87 9         329   $self->machine->run($code);
88             }
89              
90             1;
91              
92             __END__
93            
94             =pod
95            
96             =encoding UTF-8
97            
98             =head1 NAME
99            
100             Proc::tored::Manager - OO interface to creating a proctored service
101            
102             =head1 VERSION
103            
104             version 0.20
105            
106             =head1 SYNOPSIS
107            
108             my $proctor = Proc::tored::Manager->new(dir => '/tmp', name => 'my-service');
109            
110             # Call do_stuff while the service is running or until do_stuff returns false
111             $proctor->service(\&do_stuff)
112             or die sprintf('process %d is already running this service!', $proctor->running_pid);
113            
114             # Signal another process running this service to quit gracefully, throwing an
115             # error if it does not self-terminate after 15 seconds.
116             if (my $pid = $proctor->stop_wait(15)) {
117             die "process $pid is being stubborn!";
118             }
119            
120             =head1 DESCRIPTION
121            
122             Objective interface for creating and managing a proctored service.
123            
124             =head1 METHODS
125            
126             =head2 new
127            
128             Creates a new service object, which can be used to run the service and/or
129             signal another process to quit. The pid file is not created or accessed by this
130             method.
131            
132             =over
133            
134             =item name
135            
136             The name of the service. Services created with an identical L</name> and
137             L</dir> will use the same pid file and share flags.
138            
139             =item dir
140            
141             A valid run directory (C</var/run> is a common choice). The path must be
142             writable.
143            
144             =item pid_file
145            
146             Unless manually specified, the pid file's path is L</dir>/L</name>.pid.
147            
148             =item stop_file
149            
150             Unless manually specified, the stop file's path is L</dir>/L</name>.stopped.
151            
152             =item pause_file
153            
154             Unless manually specified, the pause file's path is L</dir>/L</name>.paused.
155            
156             =item trap_signals
157            
158             An optional array of signals (suitable for use in C<%SIG>) allowed to end the
159             L</service> loop. Unless specified, no signal handlers are installed.
160            
161             =back
162            
163             =head1 METHODS
164            
165             =head2 read_pid
166            
167             Returns the pid identified in the pid file. Returns 0 if the pid file does
168             not exist or is empty.
169            
170             =head2 running_pid
171            
172             Returns the pid of an already-running process or 0 if the pid file does not
173             exist, is empty, or the process identified by the pid does not exist or is not
174             visible.
175            
176             =head2 stop
177            
178             =head2 start
179            
180             =head2 is_stopped
181            
182             Controls and inspects the "stopped" flag. While stopped, the L</service> loop
183             will refuse to run.
184            
185             =head2 pause
186            
187             =head2 resume
188            
189             =head2 is_paused
190            
191             Controls and inspects the "paused" flag. While paused, the L</service> loop
192             will continue to run but will not execute the code block passed in.
193            
194             =head2 clear_flags
195            
196             Clears both the "stopped" and "paused" flags.
197            
198             =head2 is_running
199            
200             Returns true if the current process is the active, running process.
201            
202             =head2 stop_wait
203            
204             Sets the "stopped" flag and blocks until the L<running_pid> exits or the
205             C<$timeout> is reached.
206            
207             $service->stop_wait(30); # stop and block for up to 30 seconds
208            
209             =head2 service
210            
211             Accepts a code ref which will be called repeatedly until it returns false or
212             the "stopped" flag is set. If the "paused" flag is set, will continue to rune
213             but will not execute the code block until the "paused" flag has been cleared.
214            
215             Example using a pool of forked workers, an imaginary task queue, and a
216             secondary condition that decides whether to stop running.
217            
218             $proctor->service(sub {
219             # Wait for an available worker, but with a timeout
220             my $worker = $worker_pool->next_available(0.1);
221            
222             if ($worker) {
223             # Pull next task from the queue with a 0.1s timeout
224             my $task = poll_queue_with_timeout(0.1);
225            
226             if ($task) {
227             $worker->assign($task);
228             }
229             }
230            
231             return if service_should_stop();
232             return 1;
233             });
234            
235             =head1 AUTHOR
236            
237             Jeff Ober <sysread@fastmail.fm>
238            
239             =head1 COPYRIGHT AND LICENSE
240            
241             This software is copyright (c) 2017 by Jeff Ober.
242            
243             This is free software; you can redistribute it and/or modify it under
244             the same terms as the Perl 5 programming language system itself.
245            
246             =cut
247