File Coverage

blib/lib/Data/Keys/E/Dir/Lock.pm
Criterion Covered Total %
statement 48 51 94.1
branch 6 8 75.0
condition n/a
subroutine 8 8 100.0
pod 2 2 100.0
total 64 69 92.7


line stmt bran cond sub pod time code
1             package Data::Keys::E::Dir::Lock;
2              
3             =head1 NAME
4              
5             Data::Keys::E::Dir::Lock - uses additional folder to lock files
6              
7             =head1 DESCRIPTION
8              
9             Places file locks in a different folder.
10              
11             =cut
12              
13 3     3   4308 use warnings;
  3         4  
  3         121  
14 3     3   11 use strict;
  3         3  
  3         186  
15              
16             our $VERSION = '0.04';
17              
18             our $MAX_NUMBER_OF_LOCK_RETRIES = 10;
19              
20 3     3   1423 use Moose::Role;
  3         11835  
  3         12  
21 3     3   13554 use Fcntl qw(:DEFAULT :flock);
  3         5  
  3         1185  
22 3     3   15 use Carp 'confess';
  3         5  
  3         1768  
23              
24             =head1 PROPERTIES
25              
26             =head2 lock_dir
27              
28             A folder where to place locks. Default is C<< $self->base_dir / .lock >>.
29              
30             =cut
31              
32             has 'lock_dir' => ( isa => 'Str', is => 'rw', lazy => 1, default => sub { File::Spec->catdir(eval{ $_[0]->base_dir } || confess('no base_dir, do not know how to set lock_dir'), '.lock') } );
33             has '_lock_dir_data' => ( isa => 'HashRef', is => 'rw', default => sub { {} });
34              
35             requires('init');
36              
37             =head1 METHODS
38              
39             =head2 after 'init'
40              
41             Will create lock folder if not present.
42              
43             =cut
44              
45             after 'init' => sub {
46             my $self = shift;
47              
48             mkdir($self->lock_dir)
49             if (not -d $self->lock_dir);
50            
51             return;
52             };
53              
54             =head2 lock_sh
55              
56             Same as L</lock_ex>.
57              
58             =cut
59              
60             *lock_sh = *lock_ex;
61              
62             =head2 lock_ex
63              
64             Creates a locking file in C<< $self->lock_dir >> in an exclusive way.
65              
66             =cut
67              
68             sub lock_ex {
69 14     14 1 58 my $self = shift;
70 14         17 my $key = shift;
71              
72 14         17 my $lock_key = $key;
73 14         28 $lock_key =~ s{/}{_}g;
74 14         513 my $lock_filename = File::Spec->catfile($self->lock_dir, $lock_key);
75              
76 14         447 $self->_lock_dir_data->{$key}->{'counter'}++;
77             # return if already locked
78             return
79 14 100       413 if ($self->_lock_dir_data->{$key}->{'counter'} != 1);
80              
81 12         13 my $lock_fh;
82 12         16 my $num_tries = 0;
83             # try to exclusively open the lock the file, if it fails than wait until another process release the LOCK_EX
84 12         881 while (not sysopen($lock_fh, $lock_filename, O_WRONLY | O_EXCL | O_CREAT, 0644)) {
85             # wait until lock on that file is released
86 2         5 eval {
87 2         15 my $fh = IO::Any->new([$lock_filename], '+>>', { LOCK_EX => 1 });
88 0         0 close($fh);
89             };
90              
91 2         2002187 $num_tries++;
92 2 50       186 die 'failed to lock "'.$key.'" using "'.$lock_filename.'" lock file - '.$!
93             if ($num_tries > $MAX_NUMBER_OF_LOCK_RETRIES);
94             }
95 12         72 flock($lock_fh, LOCK_EX);
96 12         112 print $lock_fh $$;
97 12         419 $lock_fh->flush;
98            
99 12         501 $self->_lock_dir_data->{$key}->{'fh'} = $lock_fh;
100 12         357 $self->_lock_dir_data->{$key}->{'filename'} = $lock_filename;
101             }
102              
103             =head2 unlock
104              
105             Release a lock.
106              
107             =cut
108              
109             sub unlock {
110 11     11 1 17 my $self = shift;
111 11         14 my $key = shift;
112            
113 11 50       411 if (not $self->_lock_dir_data->{$key}) {
114 0         0 warn 'unlock("'.$key.'") but is is not locked';
115 0         0 return;
116             };
117              
118 11         371 $self->_lock_dir_data->{$key}->{'counter'}--;
119              
120 11 100       312 if ($self->_lock_dir_data->{$key}->{'counter'} <= 0) {
121             # release+delete lock file
122 9         258 unlink delete $self->_lock_dir_data->{$key}->{'filename'};
123 9         321 close delete $self->_lock_dir_data->{$key}->{'fh'};
124 9         336 delete $self->_lock_dir_data->{$key};
125             }
126             }
127              
128             sub DESTROY {
129 4     4   2915 my $self = shift;
130 4         9 my %lock_dir_data = %{ $self->_lock_dir_data };
  4         164  
131            
132 4         97 foreach my $key (keys %lock_dir_data) {
133 3         118 unlink delete $lock_dir_data{$key}->{'filename'};
134 3         160 close delete $lock_dir_data{$key}->{'fh'};
135             }
136             }
137              
138             1;
139              
140              
141             __END__
142              
143             =head1 AUTHOR
144              
145             Jozef Kutej
146              
147             =cut