File Coverage

blib/lib/File/Flock/Retry.pm
Criterion Covered Total %
statement 55 61 90.1
branch 12 22 54.5
condition 8 14 57.1
subroutine 10 12 83.3
pod 4 4 100.0
total 89 113 78.7


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