File Coverage

blib/lib/File/Pid/Quick.pm
Criterion Covered Total %
statement 52 101 51.4
branch 14 58 24.1
condition 3 27 11.1
subroutine 10 11 90.9
pod 2 2 100.0
total 81 199 40.7


line stmt bran cond sub pod time code
1             package File::Pid::Quick;
2              
3 1     1   7589 use 5.006;
  1         3  
  1         43  
4 1     1   5 use strict;
  1         2  
  1         31  
5 1     1   5 use warnings;
  1         6  
  1         56  
6              
7             =head1 NAME
8              
9             File::Pid::Quick - Quick PID file implementation
10              
11             =head1 SYNOPSIS
12              
13             use File::Pid::Quick;
14              
15             use File::Pid::Quick qw( /var/run/myjob.pid );
16              
17             use File::Pid::Quick qw( /var/run/myjob.pid verbose );
18              
19             use File::Pid::Quick qw( /var/run/myjob.pid timeout 120 );
20              
21             File::Pid::Quick->recheck;
22              
23             File::Pid::Quick->check('/var/run/myjob.pid');
24            
25             =cut
26              
27             our $VERSION = '1.02';
28              
29 1     1   6 use Carp;
  1         2  
  1         81  
30 1     1   11 use Fcntl qw( :flock );
  1         2  
  1         139  
31 1     1   5 use File::Basename qw( basename );
  1         2  
  1         120  
32 1     1   1358 use File::Spec::Functions qw( tmpdir catfile );
  1         936  
  1         1767  
33              
34             =head1 DESCRIPTION
35              
36             This module associates a PID file with your script for the purpose of
37             keeping more than one copy from running (concurrency prevention). It
38             creates the PID file, checks for its existence when the script is run,
39             terminates the script if there is already an instance running, and
40             removes the PID file when the script finishes.
41              
42             This module's objective is to provide a completely simplified interface
43             that makes adding PID-file-based concurrency prevention to your script
44             as quick and simple as possible; hence File::Pid::Quick. For a more
45             nuanced implementation of PID files, please see File::Pid.
46              
47             The absolute simplest way to use this module is:
48              
49             use File::Pid::Quick;
50              
51             A default PID file will be used, located in C<< File::Spec->tmpdir >> and
52             named C<< File::Basename::basename($0) . '.pid' >>; for example, if
53             C<$0> is F<~/bin/run>, the PID file will be F. The PID file
54             will be checked and/or generated immediately on use of the module.
55              
56             Alternately, an import list may be provided to the module. It can contain
57             three kinds of things:
58              
59             use File::Pid::Quick qw( verbose );
60              
61             If the string 'verbose' is passed in the import list, the module will do
62             more reporting on its activities than otherwise. It will use warn() for
63             its verbose output.
64              
65             use File::Pid::Quick qw( timeout 60 );
66              
67             If the string 'timeout' is passed in the import list, the next item in
68             the import list will be interpreted as a timeout after which, instead of
69             terminating itself because another instance was found, the script should
70             send a SIGTERM to the other instance and go ahead itself. The timeout
71             must be a positive integer.
72              
73             use File::Pid::Quick qw( manual );
74              
75             If the string 'manual' is passed in the import list, the normal behavior
76             of generating a default PID file will be suppressed. This is essentially
77             for cases where you want to control exactly when the PID file check is
78             performed by using File::Pid::Quick->check(), below. The check will still
79             be performed immediately if a filename is also provided in the import list.
80              
81             use File::Pid::Quick qw( /var/run/myscript.pid );
82              
83             Any other string passed in the import list is interpreted as a filename
84             to be used instead of the default for the PID file. If more than one such
85             string is found, this is an error.
86              
87             Any combination of the above import list options may be used.
88              
89             =cut
90              
91             our @pid_files_created;
92             our $verbose;
93             our $timeout;
94              
95             sub import($;@) {
96 1     1   11 my $package = shift;
97 1         2 my $filename;
98             my $manual;
99 1         7 while(scalar @_) {
100 0         0 my $item = shift;
101 0 0       0 if($item eq 'verbose') {
    0          
    0          
102 0         0 $verbose = 1;
103             } elsif($item eq 'manual') {
104 0         0 $manual = 1;
105             } elsif($item eq 'timeout') {
106 0         0 $timeout = shift;
107 0 0 0     0 unless(defined $timeout and $timeout =~ /^\d+$/ and int($timeout) eq $timeout and $timeout > 0) {
      0        
      0        
108 0 0       0 carp 'Invalid timeout ' . (defined $timeout ? '"' . $timeout . '"' : '(undefined)');
109 0         0 exit 1;
110             }
111             } else {
112 0 0       0 if(defined $filename) {
113 0         0 carp 'Invalid option "' . $item . '" (filename ' . $filename . ' already set)';
114 0         0 exit 1;
115             }
116 0         0 $filename = $item;
117             }
118             }
119 1 50 33     14 __PACKAGE__->check($filename, $timeout, 1)
      33        
120             unless $^C or ($manual and not defined $filename);
121             }
122              
123             END {
124 1     1   169 foreach my $pid_file_created (@pid_files_created) {
125             next
126 2 50       78 unless open my $pid_in, '<', $pid_file_created;
127 2         25 my $pid = <$pid_in>;
128 2         4 chomp $pid;
129 2         16 $pid =~ s/\s.*//o;
130 2 50       13 if($pid == $$) {
131 2 50       11 if($^O =~ /^MSWin/) {
132 0         0 close $pid_in;
133 0         0 undef $pid_in;
134             }
135 2 50       131 if(unlink $pid_file_created) {
136 2 50       7 warn "Deleted $pid_file_created for PID $$\n"
137             if $verbose;
138             } else {
139 0         0 warn "Could not delete $pid_file_created for PID $$\n";
140             }
141             } else {
142 0 0       0 warn "$pid_file_created had PID $pid, not $$, leaving in place\n"
143             if $verbose;
144             }
145 2 50       103 close $pid_in
146             if defined $pid_in;
147             }
148             }
149              
150             =head2 check
151              
152             File::Pid::Quick->check('/var/run/myjob.pid', 60);
153              
154             File::Pid::Quick->check(undef, undef, 1);
155              
156             Performs a check of the specified PID file, including generating it
157             if necessary, finding whether another instance is actually running,
158             and terminating the current process if necesasry.
159              
160             All arguments are optional.
161              
162             The first argument, $pid_file, is the filename to check; an undefined
163             value results in the default (described above) being used.
164              
165             The second argument, $use_timeout, is a PID file timeout. If an
166             already-running script instance started more than this many seconds
167             ago, don't terminate the current instance; instead, terminate the
168             already-running instance (by sending a SIGTERM) and proceed. If
169             defined, this must be a non-negative integer. An undefined value
170             results in the timeout value set by this module's import list being
171             used, if any; a value of 0 causes no timeout to be applied, overriding
172             the value set by the import list if necessary.
173              
174             The third argument, $warn_and_exit, controls how the script terminates.
175             If it is false, die()/croak() is used. If it is true, warn()/carp() is
176             used to issue the appropriate message and exit(1) is used to terminate.
177             This allows the module to terminate the script from inside an eval();
178             PID file checks performed based on the module's import list use this
179             option.
180              
181             =cut
182              
183             sub check($;$$$) {
184 2     2 1 292 my $package = shift;
185 2         4 my $pid_file = shift;
186 2         4 my $use_timeout = shift;
187 2         4 my $warn_and_exit = shift;
188 2 100       13 $pid_file = catfile(tmpdir, basename($0) . '.pid')
189             unless defined $pid_file;
190 2 50       226 $use_timeout = $timeout
191             unless defined $use_timeout;
192 2 0 0     7 if(defined $use_timeout and ($use_timeout =~ /\D/ or int($use_timeout) ne $use_timeout or $use_timeout < 0)) {
      33        
193 0 0       0 if($warn_and_exit) {
194 0         0 carp 'Invalid timeout "' . $use_timeout . '"';
195 0         0 exit 1;
196             } else {
197 0         0 croak 'Invalid timeout "' . $use_timeout . '"';
198             }
199             }
200 2 50       234 if(open my $pid_in, '<', $pid_file) {
201 0         0 flock $pid_in, LOCK_SH;
202 0         0 my $pid_data = <$pid_in>;
203 0         0 chomp $pid_data;
204 0         0 my $pid;
205             my $ptime;
206 0 0       0 if($pid_data =~ /(\d+)\s+(\d+)/o) {
207 0         0 $pid = $1;
208 0         0 $ptime = $2;
209             } else {
210 0         0 $pid = $pid_data;
211             }
212 0 0 0     0 if($pid != $$ and kill 0, $pid) {
213 0         0 my $name = basename($0);
214 0 0 0     0 if($timeout and $ptime < time - $timeout) {
215 0         0 my $elapsed = time - $ptime;
216 0 0       0 warn "Timing out current $name on $timeout sec vs. $elapsed sec, sending SIGTERM and rewriting $pid_file\n"
217             if $verbose;
218 0         0 kill 'TERM', $pid;
219             } else {
220 0 0       0 if($warn_and_exit) {
221 0         0 warn "Running $name found via $pid_file, process $pid, exiting\n";
222 0         0 exit 1;
223             } else {
224 0         0 die "Running $name found via $pid_file, process $pid, exiting\n";
225             }
226             }
227             }
228 0         0 close $pid_in;
229             }
230 2 50       9 unless(grep { $_ eq $pid_file } @pid_files_created) {
  1         7  
231 2         13 my $pid_out;
232 2 50       239 unless(open $pid_out, '>', $pid_file) {
233 0 0       0 if($warn_and_exit) {
234 0         0 warn "Cannot write $pid_file: $!\n";
235 0         0 exit 1;
236             } else {
237 0         0 die "Cannot write $pid_file: $!\n";
238             }
239             }
240 2         30 flock $pid_out, LOCK_EX;
241 2         57 print $pid_out $$, ' ', time, "\n";
242 2         110 close $pid_out;
243 2         5 push @pid_files_created, $pid_file;
244 2 50       65 warn "Created $pid_file for PID $$\n"
245             if $verbose;
246             }
247             }
248              
249             =head2 recheck
250              
251             File::Pid::Quick->recheck;
252              
253             File::Pid::Quick->recheck(300);
254              
255             File::Pid::Quick->recheck(undef, 1);
256              
257             Used to reverify that the running process is the owner of the
258             appropriate PID file. Checks all PID files which were created by
259             the current process.
260              
261             All arguments are optional.
262              
263             The first argument, $timeout, is a timeout value which will be
264             applied to PID file checks in exactly the same manner as describe
265             for check() above.
266              
267             The second argument, $warn_and_exit, works identically to the
268             $warn_and_exit argument described for check() above.
269              
270             =cut
271              
272             sub recheck($;$$) {
273 0     0 1   my $package = shift;
274 0           my $timeout = shift;
275 0           my $warn_and_exit = shift;
276 0 0         warn "no PID files created\n"
277             unless scalar @pid_files_created;
278 0           foreach my $pid_file_created (@pid_files_created) {
279 0           $package->check($pid_file_created, $timeout, $warn_and_exit);
280             }
281             }
282              
283             1;
284              
285             __END__