File Coverage

blib/lib/File/TinyLock.pm
Criterion Covered Total %
statement 70 89 78.6
branch 29 52 55.7
condition 2 6 33.3
subroutine 9 10 90.0
pod 1 4 25.0
total 111 161 68.9


line stmt bran cond sub pod time code
1             # File::TinyLock.pm
2             # $Id: TinyLock.pm,v 1.1 2010/01/21 14:44:16 jkister Exp $
3             # Copyright (c) 2006-2010 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   654 use strict;
  1         2  
  1         35  
120 1     1   6 use warnings;
  1         2  
  1         1512  
121              
122             my %_mylocks;
123              
124             our ($VERSION) = q$Revision: 1.1 $ =~ /(\d+\.\d+)/;
125              
126             sub new {
127 2     2 0 141 my $class = shift;
128 2         5 my %args;
129 2 50       6 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       8 die "$class: must specify lock\n" unless($args{lock});
138              
139 2         6 my $self = bless(\%args, $class);
140              
141 2         10 $self->{class} = $class;
142 2 100       7 $self->{retries} = 5 unless(defined($args{retries}));
143 2 50       15 $self->{retrydelay} = 60 unless(defined($args{retrydelay}));
144 2         5 $self->{_have_lock} = 0;
145              
146 2 50       18 if( $self->{mylock} ){
147             # must be on the same filesystem as {lock}
148 2 50       167 if($self->{lock} eq $self->{mylock}){
    50          
    50          
149 0         0 die "$class: lock and mylock may not be the same file\n";
150             }elsif( $_mylocks{ $self->{mylock} } ){
151 0         0 die "$class: already using mylock of $self->{mylock}\n";
152             }elsif( -f $self->{mylock} ){
153 0         0 die "$class: $self->{mylock} already exists\n";
154             }
155             }else{
156             # generate mylock - we could be used several times in the same code
157 0         0 for my $i (0 .. 10_000){ # could do while(1)...
158 0         0 my $mylock = $self->{lock} . $i . $$;
159 0 0 0     0 unless( $_mylocks{ $mylock } || -f $mylock ){
160 0         0 $self->{mylock} = $mylock;
161 0         0 last;
162             }
163             }
164 0 0       0 die "$class: couldnt generate mylock: 10,000 found!\n" unless( $self->{mylock} );
165             }
166 2         7 $_mylocks{ $self->{mylock} } = 1;
167              
168 2         7 return($self);
169             }
170              
171             sub lock {
172 2     2 1 9 my $self = shift;
173              
174 2     0   81 $SIG{HUP} = $SIG{QUIT} = $SIG{INT} = $SIG{TERM} = sub { $self->_debug( "caught SIG$_[0]" ); exit; };
  0         0  
  0         0  
175              
176 2 50       248 if( open( my $fh, '>', $self->{mylock} ) ){
177 2         31 print $fh "$$:$self->{mylock}\n";
178 2         102 close $fh;
179              
180 2         10 for my $try (0 .. $self->{retries}){
181 4 100       25 unless( $self->checklock() ){
182 1 50       56 if( link($self->{mylock}, $self->{lock}) ){
183 1         3 $self->{_have_lock} = 1;
184 1         3 $self->_debug( "got lock." );
185 1         8 return 1;
186             }
187             }
188 3 100 66     67 if($self->{retries} && ($try != $self->{retries})){
189 2         24 $self->_debug( "retrying in $self->{retrydelay} seconds" );
190 2 50       4000267 sleep $self->{retrydelay} unless($try == $self->{retries});
191             }
192             }
193             }else{
194 0         0 $self->_warn( "could not write to $self->{mylock}: $!" );
195             }
196 1         10 $self->_warn( "could not get lock" );
197 1         185 unlink( $self->{mylock} );
198 1         55 return 0;
199             }
200              
201             sub checklock {
202 4     4 0 10 my $self = shift;
203            
204 4 100       212 if( open(my $fh, $self->{lock}) ){
205 3         51 chomp(my $line = <$fh>);
206 3         53 close $fh;
207 3         24 my($pid,$mylock) = split(/:/, $line, 2);
208              
209 3         20 $self->_debug( "found $pid in $self->{lock}" );
210 3 50       13644 if( open(my $ps, "ps -e |") ){
211 3         27 my $stale = 1;
212 3         26533 while(<$ps>){
213 30 100       315 if(/^\s*${pid}\s*/){
214 3         9 $stale = 0;
215 3         81 $self->_debug( "found $pid is running" );
216 3         11 last;
217             }
218             }
219 3         2097 close $ps;
220              
221 3 50       21 if($stale){
222 0 0       0 unlink($mylock) || $self->_warn( "could not unlink $mylock: $!" );
223 0 0       0 unlink($self->{lock}) || die "could not unlink $self->{lock}: $!";
224 0         0 $self->_debug( "found and cleaned stale lock." );
225             }else{
226 3         18 $self->_debug( "found valid existing lock." );
227 3         85 return 1;
228             }
229              
230             }else{
231 0         0 $self->_warn( "cannot tell if lock is stale - could not fork ps: $!" );
232             }
233             }else{
234 1         31 $self->_debug( "could not read $self->{lock}: $!" );
235             }
236 1         8 return 0;
237             }
238              
239              
240             sub unlock {
241 2     2 0 19 my $self = shift;
242              
243 2 100       52 if( -f $self->{mylock} ){
244 1 50       70 unlink($self->{mylock}) || $self->_warn( "cannot unlink mylock ( $self->{mylock} ): $!" );
245             }
246              
247 2 100       7 if($self->{_have_lock}){
248 1 50       96 unlink($self->{lock}) || die "cannot unlink lock ( $self->{lock} ): $!\n";
249 1         5 $self->{_have_lock} = 0;
250             }
251             }
252              
253             sub _warn {
254 15     15   26 my $self = shift;
255 15         80 my $msg = join('', @_);
256              
257 15         441 warn "$self->{class}: $msg\n";
258             }
259              
260             sub _debug {
261 14     14   32 my $self = shift;
262              
263 14 50       102 $self->_warn(@_) if($self->{debug});
264             }
265              
266             sub DESTROY {
267 1     1   151 my $self = shift;
268              
269 1         5 $self->_debug( "cleaning up.." );
270 1         4 $self->unlock();
271              
272             }
273              
274             1;