File Coverage

blib/lib/DotLock.pm
Criterion Covered Total %
statement 9 182 4.9
branch 0 94 0.0
condition 0 6 0.0
subroutine 3 20 15.0
pod 10 10 100.0
total 22 312 7.0


line stmt bran cond sub pod time code
1             package DotLock;
2             require 5.002;
3              
4             # DotLock user documentation in POD format is at end of this file. Search for =head
5              
6 1     1   620 use strict;
  1         2  
  1         52  
7 1     1   5 use vars qw($VERSION);
  1         2  
  1         50  
8 1     1   825 use POSIX qw(uname);
  1         6990  
  1         5  
9              
10             $VERSION = "1.06";
11              
12             sub new
13             {
14 0     0 1   my($self,%args) = @_;
15              
16             # Create new object, load it with defaults.
17              
18 0           $self = {
19             timeout => 60,
20             path => "",
21             errmode => "die",
22             maxqueue => 0,
23             retrytime => 1,
24             domain => "",
25             };
26              
27             # Our marker to this object
28              
29 0           bless $self;
30              
31             # Parse Args and load object
32              
33             # errmode must be set first to allow the data methods error escaping.
34              
35 0           foreach(keys %args) {
36 0 0         if(/^-?errmode$/i) {
    0          
    0          
    0          
    0          
    0          
37 0           $self->errmode($args{$_});
38             } elsif (/^-?timeout$/i) {
39 0           $self->timeout($args{$_});
40             } elsif (/^-?path$/i) {
41 0           $self->path($args{$_});
42             } elsif (/^-?maxqueue$/i) {
43 0           $self->maxqueue($args{$_});
44             } elsif (/^-?retrytime$/i) {
45 0           $self->retrytime($args{$_});
46             } elsif (/^-?domain$/i) {
47 0           $self->domain($args{$_});
48             };
49             };
50              
51 0           $self;
52             };
53              
54             sub lock
55             {
56 0     0 1   my($self) = @_;
57 0           my(@locktarget);
58              
59             # Vars
60              
61 0           my $locktarget = $self->path;
62 0           my $locktargetfname;
63             my $lockfiledir;
64 0 0         if(-f $locktarget) {
    0          
65 0           my @locktarget = split("\/", $locktarget);
66 0           $locktargetfname = pop(@locktarget);
67 0           $lockfiledir = "\/" . join("\/", @locktarget);
68 0           push(@locktarget, "." . $locktargetfname);
69 0           $locktarget = "\/" . join("\/", @locktarget);
70             } elsif (!-d $locktarget) {
71 0           $self->_error("Lock target doesn't exist");
72 0           return undef;
73             };
74 0           my $lockfile = $locktarget . ".swp";
75 0           my $domain = $self->domain;
76 0           my $host = (POSIX::uname())[1];
77 0 0         $host =~ s/\.$domain// if(length $domain);
78 0           my $templockfile = $lockfile . ".$$-" . rand(1000) . ".$host";
79 0           my $maxqueue = $self->maxqueue;
80 0           my $timeout = $self->timeout;
81 0           my $retrytime = $self->retrytime;
82              
83             # Open temp lockfile
84              
85 0 0         unless(open L, "> $templockfile") {
86 0           $self->_error("Can't open lock file $templockfile: $!");
87 0           return undef;
88             };
89 0           $self->_add_openfiles("$templockfile");
90              
91             # Make contents informative
92             # what else can I add?
93              
94 0           print L "This lockfile was created by DotLock version $VERSION\n\n";
95 0           print L "Host: " . $host . "\n";
96 0           print L "Pid: $$\n";
97              
98             # Close and save temp lockfile
99              
100 0 0         unless(close L) {
101 0           $self->_error("Can't write lock file $templockfile: $!");
102 0           return undef;
103             };
104              
105             # We must read the dir in, find the highest lockfile in the que, and lock one higher.
106              
107             # If by the time we have read the dir, if someone tries to obtain that same lock ... we climb the que
108             # until something is free. This creates a small race condition which is only evident with fast locking.
109             # There is a better way I plan to do this, however at this point it means the loss of a feature -
110             # thoughts still ticking.
111              
112 0           opendir(LOCKFILEDIR, $lockfiledir);
113 0           my @lockfiledir = readdir(LOCKFILEDIR);
114 0           closedir(LOCKFILEDIR);
115              
116             # Find the highest free lock
117              
118 0           my $highestlock = 0;
119 0           my $mainlock = 0;
120 0           foreach (@lockfiledir) {
121 0 0         if(/^$locktargetfname\.lock_(\d)$/) {
122             # A qued lockfile
123 0 0         if($1 > $highestlock) {
124 0           $highestlock = $1;
125             };
126             };
127             };
128 0           my $freelock = $highestlock + 1;
129              
130             # Handle if queing is switched off
131              
132 0 0 0       if(($maxqueue == 0) and ($highestlock == 0)) {
133             # Hmmm, there is no-one waiting to obtain a lock, try to get the master
134 0 0         if(link($templockfile, $lockfile)) {
135             # We have the master
136 0           $self->_add_openfiles($lockfile);
137 0 0         unlink($templockfile) && $self->_del_openfiles($templockfile);
138 0           return(1);
139             } else {
140             #No master ...
141 0 0         unlink($templockfile) && $self->_del_openfiles($templockfile);
142 0           $self->_error("Could not obtain file lock, too many already queued: $lockfile");
143 0           return(undef);
144             };
145             };
146              
147             # Now its time to attempt to get a place in the que
148              
149 0           my $currentplacing;
150 0           for(my $order = $freelock; $order <= $maxqueue; $order++) {
151 0 0         if(link($templockfile, $lockfile . "_" . $order)) {
152              
153 0           $self->_add_openfiles($lockfile . "_" . $order);
154 0           $currentplacing = $order;
155              
156             # We have qued successfully, now lets move up the que
157              
158 0           alarm($timeout);
159 0           for(my $downque = $currentplacing - 1; $downque >= 1; $downque--) {
160              
161             # Clear lock files if alarmed ...
162              
163             local $SIG{ALRM} = sub {
164 0 0   0     unlink($templockfile) && $self->_del_openfiles($templockfile);
165 0 0         unlink($lockfile . "_" . $currentplacing) && $self->_del_openfiles($lockfile . "_" . $currentplacing);
166 0           $self->_error("Timed out acheiving lock: $lockfile");
167 0           return undef;
168 0           };
169 0           while(!link($templockfile, $lockfile . "_" . $downque)) {
170 0           sleep($retrytime);
171             };
172 0           $self->_add_openfiles($lockfile . "_" . $downque);
173              
174             # We have the next position, lets remove the old possy and move the marker
175              
176 0 0         unlink($lockfile . "_" . $currentplacing) && $self->_del_openfiles($lockfile . "_" . $currentplacing);
177 0           $currentplacing = $downque;
178             };
179              
180             # Now first in line ... start trying to get the main lock
181              
182             # Clear lock files if alarmed ...
183              
184             local $SIG{ALRM} = sub {
185 0 0   0     unlink($templockfile) && $self->_del_openfiles($templockfile);
186 0 0         unlink($lockfile . "_" . $currentplacing) && $self->_del_openfiles($lockfile . "_" . $currentplacing);
187 0           $self->_error("Timed out acheiving lock: $lockfile");
188 0           return undef;
189 0           };
190 0           while(!link($templockfile, $lockfile)) {
191 0           sleep($retrytime);
192             };
193 0           $self->_add_openfiles("$lockfile");
194 0           alarm(0);
195              
196             # Oh goodie, we have the main lock ... now clear the old temp lockfiles
197             # and return a positive answer to user
198              
199 0 0         unlink($lockfile . "_" . $currentplacing) && $self->_del_openfiles($lockfile . "_" . $currentplacing);
200 0 0         unlink($templockfile) && $self->_del_openfiles($templockfile);
201 0           return(1);
202             };
203             };
204            
205             # Too many waiting to que ... abort and clear files
206              
207 0 0         unlink($templockfile) && $self->_del_openfiles($templockfile);
208 0           $self->_error("Could not obtain file lock, too many already queued: $lockfile");
209 0           return undef;
210             };
211              
212             sub unlock
213             {
214 0     0 1   my($self) = @_;
215              
216 0           foreach(@{$self->_get_openfiles}) {
  0            
217 0 0         unlink($_) && $self->_del_openfiles($_);
218             };
219             };
220              
221             sub timeout
222             {
223 0     0 1   my($self, $timeout) = @_;
224              
225 0           my $prev = $self->{timeout};
226              
227 0 0         if (@_ >= 2)
228             {
229 0 0         if($timeout > 0)
230             {
231 0           $self->{timeout} = $timeout;
232             };
233             };
234              
235 0           $prev;
236             };
237              
238             sub path {
239 0     0 1   my($self, $target) = @_;
240              
241 0           my $prev = $self->{path};
242              
243              
244              
245 0 0         if (@_ >= 2) {
246 0 0         if(length $target)
247             {
248 0 0         if(-d $target) {
    0          
249 0 0         if($target !~ /\/$/) {
250 0           $target = $target . "/";
251             };
252             } elsif(!-f $target) {
253 0           $self->_error("Path $target does not exist");
254 0           return undef;
255             };
256              
257 0           $self->{path} = $target;
258             }
259             }
260              
261 0           $prev;
262             }
263              
264             sub errmode
265             {
266 0     0 1   my($self, $args) = @_;
267 0           my($prev);
268              
269 0           $prev = $self->{errmode};
270              
271 0 0         if(@_ >= 2) {
272 0 0 0       if (($args eq "die") or ($args eq "return")) {
273 0           $self->{errmode} = $args;
274             };
275             };
276              
277 0           $prev;
278             }
279              
280             sub maxqueue
281             {
282 0     0 1   my($self, $args) = @_;
283 0           my($prev);
284              
285 0           $prev = $self->{maxqueue};
286              
287 0 0         if(@_ >= 2) {
288 0 0         unless($args < 0) {
289 0           $self->{maxqueue} = $args;
290             };
291             };
292              
293 0           $prev;
294             };
295              
296             sub retrytime
297             {
298 0     0 1   my($self, $args) = @_;
299 0           my($prev);
300              
301 0           $prev = $self->{retrytime};
302              
303 0 0         if(@_ >= 2) {
304 0 0         unless($args < 0) {
305 0           $self->{retrytime} = $args;
306             };
307             };
308              
309 0           $prev;
310             };
311              
312             sub domain
313             {
314 0     0 1   my($self, $args) = @_;
315 0           my($prev);
316              
317 0           $prev = $self->{domain};
318              
319 0 0         if(@_ >= 2) {
320 0           $self->{domain} = $args;
321             };
322              
323 0           $prev;
324             };
325              
326             sub _error
327             {
328 0     0     my($self, @errmsg) = @_;
329              
330 0           my $errmsg = join('', @errmsg);
331              
332 0 0         if($self->errmode eq "die") {
    0          
333 0           $self->unlock;
334 0           die($errmsg);
335             } elsif ($self->errmode eq "return") {
336 0           $self->errmsg($errmsg);
337             };
338              
339 0           return(1);
340             };
341              
342             sub errmsg
343             {
344 0     0 1   my($self, @errmsg) = @_;
345 0           my($prev);
346              
347 0           $prev = $self->{errmsg};
348              
349 0           my $errmsg = join('', @errmsg);
350              
351 0 0         if (@_ >= 2) {
352 0 0         if(length $errmsg)
353             {
354 0           $self->{errmsg} = $errmsg;
355             };
356             };
357              
358 0           $prev;
359             };
360              
361             sub _add_openfiles
362             {
363 0     0     my($self,@files) = @_;
364              
365 0 0         if(@_ >= 2) {
366 0           foreach(@files) {
367 0           $self->{openfiles} {$_} = 1;
368             };
369             };
370              
371 0           1;
372             };
373              
374             sub _get_openfiles
375             {
376 0     0     my($self) = @_;
377 0           my @openfiles;
378              
379 0           foreach(keys %{$self->{openfiles}}) {
  0            
380 0           push(@openfiles, $_);
381             };
382              
383 0           return(\@openfiles);
384             };
385              
386             sub _del_openfiles
387             {
388 0     0     my($self,@files) = @_;
389              
390 0 0         if(@_ >= 2) {
391 0           foreach(@files) {
392 0           delete($self->{openfiles} {$_});
393             };
394             };
395              
396 0           1;
397             };
398              
399             sub DESTROY
400             {
401 0     0     my $self = shift;
402              
403 0           $self->unlock;
404             };
405              
406             1;
407              
408             __END__;