File Coverage

blib/lib/File/Flock/Retry.pm
Criterion Covered Total %
statement 55 61 90.1
branch 12 22 54.5
condition 7 14 50.0
subroutine 10 12 83.3
pod 4 4 100.0
total 88 113 77.8


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