File Coverage

blib/lib/File/TinyLock.pm
Criterion Covered Total %
statement 62 83 74.7
branch 23 50 46.0
condition 2 9 22.2
subroutine 10 11 90.9
pod 1 4 25.0
total 98 157 62.4


line stmt bran cond sub pod time code
1             # File::TinyLock.pm
2             # $Id: TinyLock.pm,v 1.20 2014/10/03 22:15:41 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   11950 use strict;
  1         1  
  1         32  
120 1     1   3 use warnings;
  1         2  
  1         840  
121              
122             my %_mylocks;
123              
124             our ($VERSION) = q$Revision: 1.20 $ =~ /(\d+\.\d+)/;
125              
126             sub new {
127 2     2 0 338 my $class = shift;
128 2         4 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         16 %args = @_;
135             }
136              
137 2 50       7 die "$class: must specify lock\n" unless($args{lock});
138              
139 2         7 my $self = bless(\%args, $class);
140              
141 2         7 $self->{class} = $class;
142 2 50       6 $self->{retries} = 5 unless(defined($args{retries}));
143 2 50       6 $self->{retrydelay} = 60 unless(defined($args{retrydelay}));
144 2         3 $self->{_have_lock} = 0;
145              
146 2 50       4 if( $self->{mylock} ){
147             # must be on the same filesystem as {lock}
148 2 50       110 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         5 $_mylocks{ $self->{mylock} } = 1;
167              
168 2         5 return($self);
169             }
170              
171             sub lock {
172 2     2 1 220 my $self = shift;
173              
174 2     0   40 $SIG{HUP} = $SIG{QUIT} = $SIG{INT} = $SIG{TERM} = sub { $self->_debug( "caught SIG$_[0]" ); exit; };
  0         0  
  0         0  
175              
176 2 50       159 if( open( my $fh, '>', $self->{mylock} ) ){
177 2         25 print $fh "$$:$self->{mylock}\n";
178 2         53 close $fh;
179              
180 2         8 for my $try (0 .. $self->{retries}){
181 2 100       6 unless( $self->checklock() ){
182 1 50       35 if( link($self->{mylock}, $self->{lock}) ){
183 1         3 $self->{_have_lock} = 1;
184 1         4 $self->_debug( "got lock." );
185 1         6 return 1;
186             }
187             }
188 1 50 33     5 if($self->{retries} && ($try != $self->{retries})){
189 0         0 $self->_debug( "retrying in $self->{retrydelay} seconds" );
190 0 0       0 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         3 $self->_warn( "could not get lock" );
197 1         68 unlink( $self->{mylock} );
198 1         8 return 0;
199             }
200              
201             sub checklock {
202 2     2 0 3 my $self = shift;
203            
204 2 100       77 if( open(my $fh, $self->{lock}) ){
205 1         7 chomp(my $line = <$fh>);
206 1         5 close $fh;
207 1         4 my($pid,$mylock) = split(/:/, $line, 2);
208              
209 1   33     4 $mylock ||= $self->{lock};
210              
211 1         6 $self->_debug( "found $pid in $self->{lock}" );
212              
213 1 50       18 if( kill(0, $pid) ){
214 1         4 $self->_debug( "found valid existing lock for pid: $pid" );
215 1         4 return 1;
216             }else{
217 0 0       0 unless( $self->{lock} eq $mylock ){
218 0 0       0 unlink($mylock) || $self->_warn( "could not unlink $mylock: $!" );
219             }
220 0 0       0 unlink($self->{lock}) || die "could not unlink $self->{lock}: $!";
221 0         0 $self->_debug( "found and cleaned stale lock." );
222             }
223              
224             }else{
225 1         11 $self->_debug( "could not read $self->{lock}: $!" );
226             }
227 1         4 return 0;
228             }
229              
230              
231             sub unlock {
232 2     2 0 3 my $self = shift;
233              
234 2 100       29 if( -f $self->{mylock} ){
235 1 50       31 unlink($self->{mylock}) || $self->_warn( "cannot unlink mylock ( $self->{mylock} ): $!" );
236             }
237              
238 2 100       36 if($self->{_have_lock}){
239 1 50       48 unlink($self->{lock}) || die "cannot unlink lock ( $self->{lock} ): $!\n";
240 1         4 $self->{_have_lock} = 0;
241             }
242             }
243              
244             sub _version {
245 1     1   6 $File::TinyLock::VERSION;
246             }
247              
248             sub _warn {
249 1     1   2 my $self = shift;
250 1         2 my $msg = join('', @_);
251              
252 1         32 warn "$self->{class}: $msg\n";
253             }
254              
255             sub _debug {
256 5     5   5 my $self = shift;
257              
258 5 50       14 $self->_warn(@_) if($self->{debug});
259             }
260              
261             sub DESTROY {
262 1     1   210 my $self = shift;
263              
264 1         3 $self->_debug( "cleaning up.." );
265 1         2 $self->unlock();
266              
267             }
268              
269             1;