File Coverage

blib/lib/Captive/Portal/LockHandle.pm
Criterion Covered Total %
statement 69 69 100.0
branch 26 30 86.6
condition 1 2 50.0
subroutine 13 13 100.0
pod 1 1 100.0
total 110 115 95.6


line stmt bran cond sub pod time code
1             package Captive::Portal::LockHandle;
2              
3 6     6   33 use strict;
  6         12  
  6         213  
4 6     6   28 use warnings;
  6         11  
  6         367  
5              
6             =head1 NAME
7              
8             Captive::Portal::LockHandle - lock handling for Captive::Portal
9              
10             =cut
11              
12             our $VERSION = '4.10';
13              
14 6     6   31 use Log::Log4perl qw(:easy);
  6         14  
  6         42  
15 6     6   4085 use Try::Tiny;
  6         15  
  6         435  
16 6     6   35 use Time::HiRes qw(usleep ualarm);
  6         12  
  6         66  
17 6     6   2340 use Fcntl qw(:flock O_CREAT O_RDWR);
  6         18  
  6         956  
18              
19 6     6   6682 use parent qw(FileHandle);
  6         2193  
  6         37  
20              
21             =head1 DESCRIPTION
22              
23             Inherit from FileHandle, add locking and DESTROY().
24              
25             =head1 CONSTRUCTION and DESTROY
26              
27             =over 4
28              
29             =item $handle = Captive::Portal::LockHandle->new(%options)
30              
31             Returns a filehandle with the requested lock assigned. There is no unlock, after destroying the filehandle the file is automatically closed and the lock released.
32              
33             Options:
34              
35             file => filename to lock, created if not existing
36             shared => shared lock, defaults to exclusive lock
37             blocking => blocking lock request, defaults to blocking
38             try => number of retries in nonblocking mode, defaults to 1 retry
39             timeout => timeout in blocking mode, defaults to 1s
40              
41             =cut
42              
43             sub new {
44 119     119 1 1470 my $self = shift;
45 119         356 my %opts = @_;
46              
47 119 50       278 LOGDIE "missing param 'file'" unless exists $opts{file};
48              
49 119         232 my $file = delete $opts{file};
50              
51 119         406 DEBUG "lock requested for $file";
52              
53             # make lexical scoped filehandle
54              
55 119 50       1120 my $lock_handle = $self->SUPER::new( $file, O_RDWR | O_CREAT )
56             or LOGDIE "Can't open $file: $!";
57              
58 119 50       12665 my $fileno = $lock_handle->fileno or LOGDIE "Can't read fileno: $!";
59              
60             # defaults
61 119 50       844 $opts{shared} = 0 unless exists $opts{shared};
62 119 100       273 $opts{blocking} = 1 unless exists $opts{blocking};
63 119 100       317 $opts{try} = 1 unless exists $opts{try};
64 119 100       317 $opts{timeout} = 1_000_000 unless exists $opts{timeout}; # 1s
65              
66 119 100       803 DEBUG "fd=$fileno, ", $opts{shared} ? 'SHARED, ' : 'EXCLUSIVE, ',
    100          
67             $opts{blocking}
68             ? "BLOCKING, timeout $opts{timeout} us"
69             : "NONBLOCKING, retry $opts{try}";
70              
71 119         774 my $mode;
72 119 100       239 if ( $opts{shared} ) {
73 11         20 $mode = LOCK_SH;
74             }
75             else {
76 108         141 $mode = LOCK_EX;
77             }
78              
79             # try to get the lock:
80             # - blocking with timeout
81             # - nonblocking with retry
82              
83 119 100       232 if ( $opts{blocking} ) {
84              
85 10         27 my $old_alarm;
86             my $error;
87              
88             try {
89              
90             local $SIG{ALRM} = sub {
91 3         143 die "fd=$fileno, timeout locking $file\n";
92 10     10   592 };
93              
94 10   50     134 $old_alarm = ualarm $opts{timeout} || 0;
95              
96 10 100       3004062 flock $lock_handle, $mode
97             or die "fd=$fileno, couldn't lock $file: $!\n";
98              
99 7         40 DEBUG "fd=$fileno, LOCKED";
100              
101             # reset alarm
102 7         151 ualarm $old_alarm;
103             }
104             catch {
105              
106             # reset alarm
107 3     3   86 ualarm $old_alarm;
108              
109             # propagate error
110 3         22 $error = $_;
111 10         109 };
112              
113 10 100       237 die "$error\n" if $error;
114              
115 7         45 return $lock_handle;
116              
117             }
118             else {
119              
120 109         158 my $error;
121              
122 109         158 $mode |= LOCK_NB;
123              
124 109         174 my $retry = $opts{try};
125              
126 109         274 while ( $retry-- > 0 ) {
127              
128 128         169 undef $error;
129              
130             try {
131 128 100   128   4986 flock $lock_handle, $mode
132             or die "fd=$fileno, couldn't lock $file: $!\n";
133              
134 108         407 DEBUG "fd=$fileno, LOCKED";
135             }
136 128     20   1046 catch { $error = $_; };
  20         275  
137              
138 128 100       2662 if ($error) {
139 20         74 DEBUG $error;
140 20         248 DEBUG "fd=$fileno, lock retries left: $retry";
141              
142             # sleep for 1ms
143 20         39163 usleep 1_000;
144              
145 20         119 next;
146             }
147              
148 108         660 return $lock_handle;
149             }
150              
151 1         14 die "$error\n";
152              
153             }
154             }
155              
156             =item $handle->DESTROY()
157              
158             Called whenever the locked filehandle is destroyed. Just implemented to get proper debug messages for locking/unlocking.
159              
160             =cut
161              
162             sub DESTROY {
163 119     119   4990 my $lock_handle = shift;
164 119         372 my $fileno = $lock_handle->fileno;
165              
166 119         845 DEBUG "fd=$fileno, UNLOCKED";
167             }
168              
169             1;
170              
171             =back
172              
173             =head1 AUTHOR
174              
175             Karl Gaissmaier, C<< >>
176              
177             =head1 LICENSE AND COPYRIGHT
178              
179             Copyright 2010-2013 Karl Gaissmaier, all rights reserved.
180              
181             This distribution is free software; you can redistribute it and/or modify it
182             under the terms of either:
183              
184             a) the GNU General Public License as published by the Free Software
185             Foundation; either version 2, or (at your option) any later version, or
186              
187             b) the Artistic License version 2.0.
188              
189             =cut
190              
191             # vim: sw=4
192