File Coverage

blib/lib/File/Flock/Retry.pm
Criterion Covered Total %
statement 54 58 93.1
branch 12 22 54.5
condition 7 12 58.3
subroutine 10 11 90.9
pod 3 3 100.0
total 86 106 81.1


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