File Coverage

blib/lib/SHARYANTO/File/Flock.pm
Criterion Covered Total %
statement 54 58 93.1
branch 11 20 55.0
condition 6 10 60.0
subroutine 10 11 90.9
pod 3 3 100.0
total 84 102 82.3


line stmt bran cond sub pod time code
1             package SHARYANTO::File::Flock;
2              
3 1     1   50384 use 5.010;
  1         3  
  1         32  
4 1     1   4 use strict;
  1         1  
  1         25  
5 1     1   3 use warnings;
  1         1  
  1         27  
6              
7 1     1   3 use Fcntl ':flock';
  1         1  
  1         491  
8              
9             our $VERSION = '0.58'; # VERSION
10              
11             sub lock {
12 4     4 1 4641 my ($class, $path, $opts) = @_;
13 4   50     17 $opts //= {};
14 4         4 my %h;
15              
16 4 50       9 defined($path) or die "Please specify path";
17 4         8 $h{path} = $path;
18 4   50     16 $h{retries} = $opts->{retries} // 60;
19              
20 4         7 my $self = bless \%h, $class;
21 4         8 $self->_lock;
22 4         9 $self;
23             }
24              
25             # return 1 if we lock, 0 if already locked. die on failure.
26             sub _lock {
27 4     4   4 my $self = shift;
28              
29             # already locked
30 4 50       11 return 0 if $self->{_fh};
31              
32 4         3 my $path = $self->{path};
33 4         28 my $existed = -f $path;
34 4         3 my $exists;
35 4         5 my $tries = 0;
36             TRY:
37 4         2 while (1) {
38 4         5 $tries++;
39              
40             # 1
41 4 50       162 open $self->{_fh}, ">>", $path
42             or die "Can't open lock file '$path': $!";
43              
44             # 2
45 4         27 my @st1 = stat($self->{_fh}); # stat before lock
46              
47             # 3
48 4 50       21 if (flock($self->{_fh}, LOCK_EX | LOCK_NB)) {
49             # if file is unlinked by another process between 1 & 2, @st1 will be
50             # empty and we check here.
51 4 50       7 redo TRY unless @st1;
52              
53             # 4
54 4         27 my @st2 = stat($path); # stat after lock
55              
56             # if file is unlinked between 3 & 4, @st2 will be empty and we check
57             # here.
58 4 50       8 redo TRY unless @st2;
59              
60             # if file is recreated between 2 & 4, @st1 and @st2 will differ in
61             # dev/inode, we check here.
62 4 50 33     18 redo TRY if $st1[0] != $st2[0] || $st1[1] != $st2[1];
63              
64             # everything seems okay
65 4         19 last;
66             } else {
67 0 0       0 $tries <= $self->{retries}
68             or die "Can't acquire lock on '$path' after $tries seconds";
69 0         0 sleep 1;
70             }
71             }
72 4         9 $self->{_created} = !$existed;
73 4         6 1;
74             }
75              
76             # return 1 if we unlock, 0 if already unlocked. die on failure.
77             sub _unlock {
78 5     5   6 my ($self) = @_;
79              
80 5         8 my $path = $self->{path};
81              
82             # don't unlock if we are not holding the lock
83 5 100       15 return 0 unless $self->{_fh};
84              
85 4 100 100     82 unlink $self->{path} if $self->{_created} && !(-s $self->{path});
86              
87             {
88             # to shut up warning about flock on closed filehandle (XXX but why
89             # closed if we are holding the lock?)
90 1     1   5 no warnings;
  1         1  
  1         138  
  4         5  
91              
92 4         16 flock $self->{_fh}, LOCK_UN;
93             }
94 4         56 close delete($self->{_fh});
95 4         42 1;
96             }
97              
98             sub release {
99 0     0 1 0 my $self = shift;
100 0         0 $self->_unlock;
101             }
102              
103             sub unlock {
104 1     1 1 224 my $self = shift;
105 1         3 $self->_unlock;
106             }
107              
108             sub DESTROY {
109 4     4   1002 my $self = shift;
110 4         8 $self->_unlock;
111             }
112              
113             1;
114             #ABSTRACT: Yet another flock module
115              
116             __END__