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   3262 use warnings;
  3         4  
  3         98  
14 3     3   11 use strict;
  3         4  
  3         117  
15              
16             our $VERSION = '0.03';
17              
18             our $MAX_NUMBER_OF_LOCK_RETRIES = 10;
19              
20 3     3   1136 use Moose::Role;
  3         9436  
  3         10  
21 3     3   11217 use Fcntl qw(:DEFAULT :flock);
  3         4  
  3         957  
22 3     3   14 use Carp 'confess';
  3         3  
  3         1431  
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 54 my $self = shift;
70 14         13 my $key = shift;
71              
72 14         13 my $lock_key = $key;
73 14         21 $lock_key =~ s{/}{_}g;
74 14         434 my $lock_filename = File::Spec->catfile($self->lock_dir, $lock_key);
75              
76 14         398 $self->_lock_dir_data->{$key}->{'counter'}++;
77             # return if already locked
78             return
79 14 100       369 if ($self->_lock_dir_data->{$key}->{'counter'} != 1);
80              
81 12         13 my $lock_fh;
82 12         14 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         698 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         3 eval {
87 2         12 my $fh = IO::Any->new([$lock_filename], '+>>', { LOCK_EX => 1 });
88 0         0 close($fh);
89             };
90              
91 2         2001948 $num_tries++;
92 2 50       157 die 'failed to lock "'.$key.'" using "'.$lock_filename.'" lock file - '.$!
93             if ($num_tries > $MAX_NUMBER_OF_LOCK_RETRIES);
94             }
95 12         59 flock($lock_fh, LOCK_EX);
96 12         83 print $lock_fh $$;
97 12         332 $lock_fh->flush;
98            
99 12         507 $self->_lock_dir_data->{$key}->{'fh'} = $lock_fh;
100 12         353 $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 15 my $self = shift;
111 11         12 my $key = shift;
112            
113 11 50       359 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         298 $self->_lock_dir_data->{$key}->{'counter'}--;
119              
120 11 100       291 if ($self->_lock_dir_data->{$key}->{'counter'} <= 0) {
121             # release+delete lock file
122 9         246 unlink delete $self->_lock_dir_data->{$key}->{'filename'};
123 9         287 close delete $self->_lock_dir_data->{$key}->{'fh'};
124 9         305 delete $self->_lock_dir_data->{$key};
125             }
126             }
127              
128             sub DESTROY {
129 4     4   2888 my $self = shift;
130 4         6 my %lock_dir_data = %{ $self->_lock_dir_data };
  4         159  
131            
132 4         91 foreach my $key (keys %lock_dir_data) {
133 3         110 unlink delete $lock_dir_data{$key}->{'filename'};
134 3         181 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