File Coverage

blib/lib/File/TinyLock.pm
Criterion Covered Total %
statement 71 90 78.8
branch 29 52 55.7
condition 2 6 33.3
subroutine 9 10 90.0
pod 1 4 25.0
total 112 162 69.1


line stmt bran cond sub pod time code
1             # File::TinyLock.pm
2             # $Id: TinyLock.pm,v 1.11 2014/09/12 20:15:33 jkister Exp $
3             # Copyright (c) 2006-2014 Jeremy Kister.
4             # Released under Perl's Artistic License.
5              
6             =head1 NAME
7              
8             File::TinyLock - Utility for process locking and unlocking.
9              
10             =head1 SYNOPSIS
11              
12             use File::TinyLock;
13              
14             my $LOCK = '/tmp/testing.lock';
15              
16             my $locksmith = File::TinyLock->new(lock => $LOCK);
17             if( $locksmith->lock() ){
18             warn "we have locked\n";
19             $locksmith->unlock();
20             warn "we have unlocked\n";
21             }else{
22             warn "could not lock..\n";
23             }
24            
25             =head1 DESCRIPTION
26              
27             C provides C, C, and C methods for
28             working with process locking. This utility attempts to be useful when you
29             require one of a process to be running at a time, but someone could possibly
30             try to spawn off a second (such as having a crontab where you are *hoping*
31             one job ends before the next starts).
32              
33             =head1 CONSTRUCTOR
34              
35             =over 4
36              
37             =item lock( [LOCK] [,OPTIONS] );
38              
39             C is a mandatory lock file.
40              
41             C are passed in a hash like fashion, using key and value
42             pairs. Possible options are:
43              
44             B - Unique file to identify our process (Default: auto-generated)
45             - *must* be on the same filesystem as
46              
47             B - Number of times to retry getting a lock (Default: 5)
48              
49             B - Number of seconds to wait between retries (Default: 60)
50              
51             B - Print debugging info to STDERR (0=Off, 1=On) (Default: 0).
52              
53             =head1 RETURN VALUE
54              
55             Here are a list of return codes of the C function and what they mean:
56              
57             =item 0 The process could not get a lock.
58              
59             =item 1 The process has obtained a lock.
60              
61             .. and for the C function:
62              
63             =item 0 The process does not have a lock.
64              
65             =item 1 The process has a lock.
66              
67             .. and the C function:
68              
69             =item Note: method will die if we cannot modify
70              
71             =head1 EXAMPLES
72              
73             # run the below code twice ( e.g. perl ./test.pl & ; perl ./test.pl )
74              
75             use strict;
76             use File::TinyLock;
77              
78             my $lock = '/tmp/testing.lock';
79             my $locksmith = File::TinyLock->new(lock => $lock, debug => 1);
80              
81             my $result = $locksmith->lock();
82             if($result){
83             print "We have obtained a lock\n";
84             }
85            
86             # do stuff
87              
88             sleep 30;
89              
90             $locksmith->unlock();
91             exit;
92              
93              
94             =head1 CAVEATS
95              
96             If you leave lock files around (from not unlocking the file before
97             your code exits), C will try its best to clean up
98             and/or determine if the lock files are stale or not. This is best
99             effort, and may yield false positives. For example, if your code
100             was running as pid 1234 and crashed without unlocking, stale
101             detection may fail if there is a new process running with pid 1234.
102              
103             =head1 RESTRICTIONS
104              
105             Locking will only remain successfull while your code is active. You
106             can not lock, let your code exit, and start your code again - doing
107             so will result in stale lock files left behind.
108              
109             start code -> get lock -> do stuff -> unlock -> exit;
110              
111             =head1 AUTHOR
112              
113             Jeremy Kister
114              
115             =cut
116              
117             package File::TinyLock;
118              
119 1     1   1758 use strict;
  1         2  
  1         39  
120 1     1   4 use warnings;
  1         2  
  1         1601  
121              
122             my %_mylocks;
123              
124             our ($VERSION) = q$Revision: 1.11 $ =~ /(\d+\.\d+)/;
125              
126             sub new {
127 2     2 0 124 my $class = shift;
128 2         4 my %args;
129 2 50       8 if(@_ % 2){
130 0         0 my $lock = shift;
131 0         0 %args = @_;
132 0         0 $args{lock} = $lock;
133             }else{
134 2         11 %args = @_;
135             }
136              
137 2 50       7 die "$class: must specify lock\n" unless($args{lock});
138 2         12 $ENV{PATH} .= '/bin:/usr/bin';
139              
140 2         5 my $self = bless(\%args, $class);
141              
142 2         8 $self->{class} = $class;
143 2 100       7 $self->{retries} = 5 unless(defined($args{retries}));
144 2 50       16 $self->{retrydelay} = 60 unless(defined($args{retrydelay}));
145 2         4 $self->{_have_lock} = 0;
146              
147 2 50       6 if( $self->{mylock} ){
148             # must be on the same filesystem as {lock}
149 2 50       148 if($self->{lock} eq $self->{mylock}){
    50          
    50          
150 0         0 die "$class: lock and mylock may not be the same file\n";
151             }elsif( $_mylocks{ $self->{mylock} } ){
152 0         0 die "$class: already using mylock of $self->{mylock}\n";
153             }elsif( -f $self->{mylock} ){
154 0         0 die "$class: $self->{mylock} already exists\n";
155             }
156             }else{
157             # generate mylock - we could be used several times in the same code
158 0         0 for my $i (0 .. 10_000){ # could do while(1)...
159 0         0 my $mylock = $self->{lock} . $i . $$;
160 0 0 0     0 unless( $_mylocks{ $mylock } || -f $mylock ){
161 0         0 $self->{mylock} = $mylock;
162 0         0 last;
163             }
164             }
165 0 0       0 die "$class: couldnt generate mylock: 10,000 found!\n" unless( $self->{mylock} );
166             }
167 2         7 $_mylocks{ $self->{mylock} } = 1;
168              
169 2         6 return($self);
170             }
171              
172             sub lock {
173 2     2 1 10 my $self = shift;
174              
175 2     0   74 $SIG{HUP} = $SIG{QUIT} = $SIG{INT} = $SIG{TERM} = sub { $self->_debug( "caught SIG$_[0]" ); exit; };
  0         0  
  0         0  
176              
177 2 50       176 if( open( my $fh, '>', $self->{mylock} ) ){
178 2         23 print $fh "$$:$self->{mylock}\n";
179 2         92 close $fh;
180              
181 2         8 for my $try (0 .. $self->{retries}){
182 4 100       52 unless( $self->checklock() ){
183 1 50       44 if( link($self->{mylock}, $self->{lock}) ){
184 1         2 $self->{_have_lock} = 1;
185 1         3 $self->_debug( "got lock." );
186 1         6 return 1;
187             }
188             }
189 3 100 66     90 if($self->{retries} && ($try != $self->{retries})){
190 2         23 $self->_debug( "retrying in $self->{retrydelay} seconds" );
191 2 50       4000365 sleep $self->{retrydelay} unless($try == $self->{retries});
192             }
193             }
194             }else{
195 0         0 $self->_warn( "could not write to $self->{mylock}: $!" );
196             }
197 1         14 $self->_warn( "could not get lock" );
198 1         191 unlink( $self->{mylock} );
199 1         27 return 0;
200             }
201              
202             sub checklock {
203 4     4 0 11 my $self = shift;
204            
205 4 100       299 if( open(my $fh, $self->{lock}) ){
206 3         74 chomp(my $line = <$fh>);
207 3         72 close $fh;
208 3         24 my($pid,$mylock) = split(/:/, $line, 2);
209              
210 3         29 $self->_debug( "found $pid in $self->{lock}" );
211 3 50       10634 if( open(my $ps, "ps -e |") ){
212 3         26 my $stale = 1;
213 3         12706 while(<$ps>){
214 30 100       322 if(/^\s*${pid}\s*/){
215 3         12 $stale = 0;
216 3         141 $self->_debug( "found $pid is running" );
217 3         12 last;
218             }
219             }
220 3         160 close $ps;
221              
222 3 50       15 if($stale){
223 0 0       0 unlink($mylock) || $self->_warn( "could not unlink $mylock: $!" );
224 0 0       0 unlink($self->{lock}) || die "could not unlink $self->{lock}: $!";
225 0         0 $self->_debug( "found and cleaned stale lock." );
226             }else{
227 3         18 $self->_debug( "found valid existing lock." );
228 3         115 return 1;
229             }
230              
231             }else{
232 0         0 $self->_warn( "cannot tell if lock is stale - could not fork ps: $!" );
233             }
234             }else{
235 1         27 $self->_debug( "could not read $self->{lock}: $!" );
236             }
237 1         7 return 0;
238             }
239              
240              
241             sub unlock {
242 2     2 0 17 my $self = shift;
243              
244 2 100       51 if( -f $self->{mylock} ){
245 1 50       65 unlink($self->{mylock}) || $self->_warn( "cannot unlink mylock ( $self->{mylock} ): $!" );
246             }
247              
248 2 100       8 if($self->{_have_lock}){
249 1 50       85 unlink($self->{lock}) || die "cannot unlink lock ( $self->{lock} ): $!\n";
250 1         48 $self->{_have_lock} = 0;
251             }
252             }
253              
254             sub _warn {
255 15     15   24 my $self = shift;
256 15         62 my $msg = join('', @_);
257              
258 15         712 warn "$self->{class}: $msg\n";
259             }
260              
261             sub _debug {
262 14     14   35 my $self = shift;
263              
264 14 50       92 $self->_warn(@_) if($self->{debug});
265             }
266              
267             sub DESTROY {
268 1     1   217 my $self = shift;
269              
270 1         4 $self->_debug( "cleaning up.." );
271 1         4 $self->unlock();
272              
273             }
274              
275             1;