File Coverage

blib/lib/Pid/File/Flock.pm
Criterion Covered Total %
statement 57 86 66.2
branch 22 66 33.3
condition 5 21 23.8
subroutine 14 16 87.5
pod 5 5 100.0
total 103 194 53.0


line stmt bran cond sub pod time code
1             package Pid::File::Flock;
2              
3 3     3   74939 use warnings qw(all);
  3         9  
  3         161  
4 3     3   26 use strict;
  3         5  
  3         183  
5              
6             =head1 NAME
7              
8             Pid::File::Flock - PID file operations
9              
10             =head1 VERSION
11              
12             Version 0.08
13              
14             =cut
15              
16             our $VERSION = '0.08';
17              
18             =head1 SYNOPSIS
19              
20             You can use module generic way:
21              
22             use Pid::File::Flock;
23             ...
24             Pid::File::Flock->new;
25             Pid::File::Flock->new('file');
26             Pid::File::Flock->new(debug=>1, dir=>'/tmp');
27              
28             or in simplified form:
29              
30             use Pid::File::Flock qw(:auto);
31             use Pid::File::Flock qw(:auto :raise);
32             use Pid::File::Flock qw(:auto path=file);
33             use Pid::File::Flock qw(:auto :debug dir=/tmp);
34              
35             you can mix both too:
36              
37             use Pid::File::Flock qw(:debug dir=/tmp);
38             ...
39             Pid::File::Flock->new(ext=>'.old');
40              
41             =cut
42              
43 3     3   17 use Carp;
  3         10  
  3         280  
44 3     3   18 use Fcntl qw(:DEFAULT :flock :seek);
  3         5  
  3         1929  
45 3     3   19 use File::Basename qw(basename);
  3         12  
  3         191  
46 3     3   3530 use File::Spec::Functions qw(catfile rel2abs tmpdir);
  3         2848  
  3         1990  
47              
48             my ($inst,%iopts);
49              
50              
51             =head1 IMPORT LIST
52              
53             You can provide 'flag' options ('debug','quiet') like an import tag:
54             C
55              
56             Valued options can be specified with key=value form:
57             C
58              
59             Pseudo tag ':auto' create lock object implicitly.
60              
61              
62             =head1 GENERIC USAGE
63              
64             =head2 new( $path, %options )
65              
66             Generic constructor
67              
68             =over
69              
70             =item $path
71              
72             Optional argument, if provided options 'dir','name'
73             and 'ext' will be silently ignored.
74              
75             =back
76              
77             Supported options:
78              
79             =over
80              
81             =item * dir => 'directory'
82              
83             Base directory for pid file (by default File::Spec::tmpdir called).
84              
85             =item * name => 'basename'
86              
87             Name for pid file (by default like a script self).
88              
89             =item * ext => 'extension'
90              
91             Extension for pid file ('.pid' by default).
92              
93             =item * raise => 1
94              
95             Use C instead of simple C.
96             Usable from caller eval block to handle unsuccessful locking attempts.
97              
98             =item * debug => 1
99              
100             Switch debug mode on (some information via STDERR).
101              
102             =item * quiet => 1
103              
104             Switch quiet mode on (don't warn about staled pid files).
105              
106             =back
107              
108             =cut
109              
110 2   33 2 1 102 sub new { $inst ||= shift->acquire(@_) }
111              
112              
113             =head2 abandon
114              
115             Don't try to remove pid file during destruction.
116             Become for using in forking applications.
117              
118             =cut
119              
120 0     0 1 0 sub abandon { $inst->{abandoned}=1 }
121              
122              
123             =head1 INTERNAL ROUTINES
124              
125             You haven't call these methods directly.
126              
127             =head2 import
128              
129             Process 'fake' import list.
130              
131             =cut
132              
133             sub import {
134 3     3   35 shift;
135 3         11 for (@_) {
136 2 100       11 /^:(.+)/ && do { # :flag
137 1         17 $iopts{$1} = 1; next
138 1         3 };
139 1 50       7 /^([^=]+)=([^=]+)$/ && do { # key=value
140 1         3 $iopts{$1} = $2; next
141 1         3 };
142 0         0 croak "invalid import list statement: $_";
143             }
144             # auto lock
145 3 100       157776 __PACKAGE__->new($iopts{path}) if $iopts{auto};
146             }
147              
148              
149             =head2 acquire
150              
151             Acquiring lock, called by C constructor.
152              
153             =cut
154              
155             sub acquire {
156 2     2 1 6 my $proto = shift;
157 2 50       14 my $path = shift if @_%2;
158 2         10 my %opts = (wait=>0,%iopts,@_);
159              
160 2 50       9 undef $opts{quiet} if $opts{debug}; # mutually exclusive
161              
162             # construct and normalize path
163 2   66     96 $path = rel2abs $path || catfile $opts{dir}||tmpdir, $opts{name}||(basename($0).($opts{ext}||'.pid'));
164 2 50       112 carp "started, pid $$ ($path)" if $opts{debug};
165              
166             # try to get locked handle
167 2         12 my $fh = attempt($path,%opts);
168              
169             # unsuccessfully locking
170 2 50       12 unless ($fh) {
171             # waiting for lock
172 0 0       0 if ($opts{wait}) {
173 0     0   0 local $SIG{ALRM} = sub { die "x\n" };
  0         0  
174 0         0 alarm $opts{wait};
175 0         0 eval {
176 0         0 do {
177             # try to get locked handle (blocking)
178 0 0       0 carp "found alive process, waiting $opts{wait}" if $opts{debug};
179 0         0 $fh = attempt($path,%opts,block=>1);
180             } until $fh;
181 0         0 alarm 0;
182             };
183             # catched die to croak
184 0 0 0     0 croak $1 if $@ && $@ ne "x\n" && $@ =~ /^(.+)\n?/;
      0        
185 0 0       0 goto LOCKED if $fh;
186             }
187             # get pid of alive process
188 0 0 0     0 if ( $opts{raise} || !$opts{quiet}) {
189 0 0       0 sysopen FH, $path, O_RDONLY or do {
190 3 0   3   3466 croak "can't read pid file ($path): $!" unless $!{ENOENT};
  3         4695  
  3         2115  
  0         0  
191             };
192 0 0       0 my $ex = $opts{raise} ? \&croak : \&carp;
193 0         0 &$ex("found alive process (".."), exit");
194             }
195             # gently terminate main process
196 0         0 exit;
197             }
198              
199             LOCKED:
200             # warning about staled pid
201 2 50       12 if ($opts{debug}) {
202 0         0 carp "found staled pid file (".<$fh>.")";
203 0 0       0 sysseek $fh,0,SEEK_SET or croak "can't seek in pid file ($path): $!"
204             }
205 2 50 33     244 truncate $fh,0 and syswrite $fh,$$ or croak "can't write pid file ($path): $!";
206 2         132 bless { path => $path, handle => $fh, debug => $opts{debug} }, $proto;
207             }
208              
209             =head2 attempt
210              
211             Attempting acquire lock with additional checks.
212              
213             =cut
214              
215             sub attempt {
216 2     2 1 8 my ($path,%opts) = @_;
217              
218             # just an open
219 2 50       43451 sysopen FH, $path, O_CREAT|O_RDWR or croak "can't open pid file ($path): $!";
220              
221             # try to lock it
222 2 50       16 my $nb = $opts{block} ? 0 : LOCK_NB;
223 2 50       25 flock FH, LOCK_EX|$nb or do {
224 0 0       0 croak "can't lock pid file ($path): $!" unless $!{EAGAIN};
225 0         0 return;
226             };
227              
228             # exclusive locking on win32 is sufficient condition
229 2 50       20 return *FH if $^O eq 'MSWin32';
230              
231             # ok, now we have locked handle, but is original file name steel exists?
232 2 50       45 my @stath = stat FH or croak "can't get stat about locked handle: $!";
233 2 50       65 my @statf = stat $path or do {
234 0 0       0 croak "can't get stat about file ($path): $!" unless $!{ENOENT};
235             # recursive call, no more tries
236 0 0       0 return if $opts{recurs};
237             # try to recreate dir entry (non-blocking recursive call)
238 0 0       0 carp "dir entry for pid file was lost" if $opts{debug};
239 0         0 return attempt($path,%opts,block=>0,recurs=>1);
240             };
241              
242             # there is new dir entry, our locked handle is invalid now
243 2 50 33     32 unless ($stath[0] == $statf[0] && $stath[1] == $statf[1]) {
244 0 0       0 carp "dir entry for pid file was recreated" if $opts{debug};
245 0         0 return;
246             }
247              
248 2         18 return *FH;
249             }
250              
251             =head2 release
252              
253             Unlink pid file, handle will be closed a bit later, during object destructing.
254              
255             =cut
256              
257             sub release {
258 4     4 1 1530 my $self = shift;
259 4 100       18 return undef $inst unless ref $self;
260 2         33 close $self->{handle};
261 2 50       237 unlink $self->{path} or carp "can't remove pid file ($self->{path}): $!";
262             }
263              
264              
265             =head2 DESTROY
266              
267             Lock object destructor.
268              
269             =cut
270              
271 2 50   2   36 sub DESTROY { $_[0]->{abandoned} or shift->release }
272              
273              
274             =head2 END
275              
276             Undefine module lexical variable to force DESTROY invoking.
277              
278             =cut
279              
280 3     3   1343 sub END { undef $inst }
281              
282             1;
283              
284             __END__