File Coverage

blib/lib/XAS/Lib/Lockmgr/Filesystem.pm
Criterion Covered Total %
statement 12 114 10.5
branch 0 50 0.0
condition 0 7 0.0
subroutine 4 27 14.8
pod 6 7 85.7
total 22 205 10.7


line stmt bran cond sub pod time code
1             package XAS::Lib::Lockmgr::Filesystem;
2              
3             our $VERSION = '0.02';
4              
5 1     1   926 use DateTime;
  1         1  
  1         23  
6 1     1   394 use Try::Tiny::Retry ':all';
  1         869  
  1         141  
7 1     1   5 use XAS::Constants 'TRUE FALSE HASHREF';
  1         1  
  1         8  
8              
9             use XAS::Class
10 1         8 debug => 0,
11             version => $VERSION,
12             base => 'XAS::Base',
13             utils => 'dotid',
14             import => 'class',
15             filesystem => 'Dir File',
16             vars => {
17             PARAMS => {
18             -key => 1,
19             -args => { optional => 1, type => HASHREF, default => {} },
20             }
21             }
22 1     1   131 ;
  1         1  
23              
24             #use Data::Dumper;
25              
26             # note to self: Don't put $self->log->debug() statements in here, it
27             # produces a nice race condidtion.
28              
29             # ----------------------------------------------------------------------
30             # Overrides
31             # ----------------------------------------------------------------------
32              
33             class('Badger::Filesystem')->methods(
34             directory_exists => sub {
35 0     0     my $self = shift;
36 0           my $dir = shift;
37 0   0       my $stats = $self->stat_path($dir) || return;
38 0 0         return -d $dir ? $stats : 0; # don't use the cached stat
39             },
40             file_exists => sub {
41 0     0     my $self = shift;
42 0           my $file = shift;
43 0   0       my $stats = $self->stat_path($file) || return;
44 0 0         return -f $file ? $stats : 0; # don't use the cached stat
45             }
46             );
47              
48             # ----------------------------------------------------------------------
49             # Public Methods
50             # ----------------------------------------------------------------------
51              
52             sub lock {
53 0     0 1   my $self = shift;
54              
55 0           my $stat = FALSE;
56 0           my $lock = $self->_lockfile();
57 0           my $limit = $self->args->{'limit'};
58 0           my $timeout = $self->args->{'timeout'};
59 0           my $dir = Dir($lock->volume, $lock->directory);
60              
61             retry {
62              
63 0 0   0     if ($^O ne 'MSWin32') {
64              
65             # temporarily change the umask to create the
66             # directory and files with correct file permissions
67              
68 0           my $omode = umask(0012);
69 0           $dir->create;
70 0           $lock->create;
71 0           umask($omode);
72              
73             } else {
74              
75 0           $dir->create;
76 0           $lock->create;
77              
78             }
79              
80 0           $stat = TRUE;
81              
82             } retry_if {
83              
84 0     0     1; # always retry
85              
86             } delay_exp {
87              
88 0     0     $limit, $timeout * 1000
89              
90             } catch {
91              
92 0     0     my $ex = $_;
93 0 0         my $msg = (ref($ex) eq 'Badger::Exception') ? $ex->info : $ex;
94            
95 0           $self->throw_msg(
96             dotid($self->class) . '.lock',
97             'lock_error',
98             $msg
99             );
100              
101 0           };
102              
103 0           return $stat;
104              
105             }
106              
107             sub unlock {
108 0     0 1   my $self = shift;
109              
110 0           my $stat = FALSE;
111 0           my $lock = $self->_lockfile();
112 0           my $limit = $self->args->{'limit'};
113 0           my $timeout = $self->args->{'timeout'};
114 0           my $dir = Dir($lock->volume, $lock->directory);
115              
116             retry {
117              
118 0 0   0     if ($dir->exists) {
119              
120 0 0         if ($lock->exists) {
121              
122 0 0         $lock->delete if ($lock->exists);
123 0 0         $dir->delete if ($dir->exists);
124 0           $stat = TRUE;
125              
126             } else {
127              
128 0 0         $dir->delete if($dir->exists);
129 0           $stat = TRUE;
130              
131             }
132              
133             }
134              
135             } retry_if {
136              
137 0     0     1; # always retry
138              
139             } delay_exp {
140              
141 0     0     $limit, $timeout * 1000
142              
143             } catch {
144              
145 0     0     my $ex = $_;
146 0 0         my $msg = (ref($ex) eq 'Badger::Exception') ? $ex->info : $ex;
147              
148 0           $self->throw_msg(
149             dotid($self->class) . '.unlock',
150             'lock_error',
151             $msg
152             );
153              
154 0           };
155              
156 0           return $stat;
157              
158             }
159              
160             sub try_lock {
161 0     0 1   my $self = shift;
162              
163 0           my $lock = $self->_lockfile();
164              
165 0 0         return $lock->exists ? FALSE : TRUE;
166              
167             }
168              
169             sub break_lock {
170 0     0 1   my $self = shift;
171              
172 0           my $lock = $self->_lockfile();
173 0           my $dir = Dir($lock->volume, $lock->directory);
174              
175             try {
176              
177 0 0   0     if ($dir->exists) {
178              
179 0           foreach my $file (@{$dir->files}) {
  0            
180              
181 0 0         $file->delete if ($file->exists);
182              
183             }
184              
185 0 0         $dir->delete if ($dir->exists);
186              
187             }
188              
189             } catch {
190              
191 0     0     my $ex = $_;
192 0 0         my $msg = (ref($ex) eq 'Badger::Exception') ? $ex->info : $ex;
193              
194 0           $self->throw_msg(
195             dotid($self->class) . '.break_lock',
196             'lock_error',
197             $msg
198             );
199              
200 0           };
201              
202             }
203              
204             sub whose_lock {
205 0     0 1   my $self = shift;
206              
207 0           my $pid = $$;
208 0           my $host = $self->env->host;
209 0           my $time = DateTime->now(time_zoned => 'local');
210 0           my $lock = $self->_lockfile();
211 0           my $dir = Dir($lock->volume, $lock->directory);
212              
213             try {
214              
215 0 0   0     if ($dir->exists) {
216              
217 0 0         if (my @files = $dir->files) {
218              
219             # should only be one file in the directory
220              
221 0 0         if ($files[0]->exists) {
222              
223 0           $host = $files[0]->basename;
224 0           $pid = $files[0]->extension;
225 0           $time = DateTime->from_epoch(
226             epoch => ($files[0]->stat)[9],
227             time_zone => 'local'
228             );
229              
230             }
231              
232             }
233              
234             }
235              
236             } catch {
237              
238 0     0     my $ex = $_;
239 0 0         my $msg = (ref($ex) eq 'Badger::Exception') ? $ex->info : $ex;
240              
241 0           $self->throw_msg(
242             dotid($self->class) . '.whose_lock',
243             'lock_error',
244             $msg
245             );
246              
247 0           };
248              
249 0           return $host, $pid, $time;
250              
251             }
252              
253             sub destroy {
254 0     0 0   my $self = shift;
255              
256 0           my $lock = $self->_lockfile();
257 0           my $dir = Dir($lock->volume, $lock->directory);
258 0           my ($host, $pid, $time) = $self->whose_lock();
259              
260 0 0 0       if (($host eq $self->env->host) && ($pid = $$)) {
261              
262 0 0         $lock->delete if ($lock->exists);
263 0 0         $dir->delete if ($dir->exists);
264              
265             }
266              
267             }
268              
269             sub DESTROY {
270 0     0     my $self = shift;
271              
272 0           $self->destroy();
273              
274             }
275              
276             # ----------------------------------------------------------------------
277             # Private Methods
278             # ----------------------------------------------------------------------
279              
280             sub _lockfile {
281 0     0     my $self = shift;
282              
283 0           my $extension = ".$$";
284 0           my $name = $self->env->host;
285              
286 0           return File($self->key, $name . $extension);
287              
288             }
289              
290             sub init {
291 0     0 1   my $class = shift;
292              
293 0           my $self = $class->SUPER::init(@_);
294 0           my $key = Dir($self->{'key'});
295              
296 0 0         if ($key->is_relative) {
297              
298 0           $self->{'key'} = Dir($self->env->locks, $self->{'key'});
299              
300             }
301              
302 0 0         $self->args->{'limit'} = 10 unless defined($self->args->{'limit'});
303 0 0         $self->args->{'timeout'} = 10 unless defined($self->args->{'timeout'});
304              
305 0           return $self;
306              
307             }
308              
309             1;
310              
311             __END__