File Coverage

lib/IPC/SRLock/Fcntl.pm
Criterion Covered Total %
statement 50 50 100.0
branch 2 4 100.0
condition 4 6 100.0
subroutine 14 14 100.0
pod 3 3 100.0
total 73 77 100.0


line stmt bran cond sub pod time code
1             package IPC::SRLock::Fcntl;
2              
3 1     1   464 use namespace::autoclean;
  1         2  
  1         5  
4              
5 1     1   60 use English qw( -no_match_vars );
  1         1  
  1         17  
6 1     1   274 use File::DataClass::Constants qw( LOCK_BLOCKING LOCK_NONBLOCKING );
  1         1  
  1         48  
7 1         5 use File::DataClass::Types qw( Directory NonEmptySimpleStr
8 1     1   4 OctalNum Path PositiveInt RegexpRef );
  1         1  
9 1     1   724 use File::Spec;
  1         1  
  1         22  
10 1         4 use IPC::SRLock::Utils qw( Unspecified hash_from loop_until
11 1     1   4 merge_attributes throw );
  1         0  
12 1     1   893 use Storable qw( nfreeze thaw );
  1         2287  
  1         55  
13 1     1   6 use Try::Tiny;
  1         1  
  1         41  
14 1     1   3 use Moo;
  1         2  
  1         5  
15              
16             extends q(IPC::SRLock::Base);
17              
18             # Attribute constructors
19             my $_build__lockfile = sub {
20 2     2   528 my $self = shift; my $path = $self->_lockfile_name;
  2         6  
21              
22             # uncoverable condition false
23 2   66     11 $path ||= $self->tempdir->catfile( $self->name.'.lck' );
24             # uncoverable branch true
25 2 50       618 $path =~ $self->pattern or throw 'Path [_1] cannot untaint', [ $path ];
26              
27 2         38 return $path;
28             };
29              
30             my $_build__shmfile = sub {
31 2     2   549 my $self = shift; my $path = $self->_shmfile_name;
  2         7  
32              
33             # uncoverable condition false
34 2   66     15 $path ||= $self->tempdir->catfile( $self->name.'.shm' );
35             # uncoverable branch true
36 2 50       667 $path =~ $self->pattern or throw 'Path [_1] cannot untaint', [ $path ];
37              
38 2         48 return $path;
39             };
40              
41             # Public attributes
42             has 'mode' => is => 'ro', isa => OctalNum, coerce => 1, default => '0666';
43              
44             has 'pattern' => is => 'ro', isa => RegexpRef,
45             default => sub { qr{ \A ([ ~:+./\-\\\w]+) \z }msx };
46              
47             has 'tempdir' => is => 'ro', isa => Directory, coerce => 1,
48             default => sub { File::Spec->tmpdir };
49              
50             has 'umask' => is => 'ro', isa => PositiveInt, default => 0;
51              
52             # Private attributes
53             has '_lockfile' => is => 'lazy', isa => Path, coerce => 1,
54             builder => $_build__lockfile;
55              
56             has '_lockfile_name' => is => 'ro', isa => NonEmptySimpleStr,
57             init_arg => 'lockfile';
58              
59             has '_shmfile' => is => 'lazy', isa => Path, coerce => 1,
60             builder => $_build__shmfile;
61              
62             has '_shmfile_name' => is => 'ro', isa => NonEmptySimpleStr,
63             init_arg => 'shmfile';
64              
65             # Construction
66             around 'BUILDARGS' => sub {
67             my ($orig, $self, @args) = @_; my $attr = $orig->( $self, @args );
68              
69             my $builder = $attr->{builder} or return $attr;
70             my $config = $builder->can( 'config' ) ? $builder->config : {};
71              
72             merge_attributes $attr, $config, [ 'tempdir' ];
73              
74             return $attr;
75             };
76              
77             # Private methods
78             my $_expire_lock = sub {
79             my ($self, $content, $key, $lock) = @_;
80              
81             $self->log->error
82             ( $self->_timeout_error
83             ( $key, $lock->{spid}, $lock->{stime}, $lock->{timeout} ) );
84              
85             delete $content->{ $key };
86             return 0;
87             };
88              
89             my $_read_shmfile = sub {
90             my ($self, $async) = @_; my ($file, $content);
91              
92             my $old_umask = umask $self->umask;
93             my $mode = $async ? LOCK_NONBLOCKING : LOCK_BLOCKING;
94             my $shmfile = $self->_shmfile;
95              
96             try {
97             $file = $self->_lockfile->lock( $mode )->assert_open( 'w', $self->mode );
98             }
99             catch { umask $old_umask; throw $_ };
100              
101             if ($file->have_lock and $shmfile->exists) {
102             try { $content = thaw $shmfile->all }
103             catch { $file->close; umask $old_umask; throw $_ };
104             }
105             else { $content = {} }
106              
107             $shmfile->close; umask $old_umask;
108             return ($file, $content);
109             };
110              
111             my $_unlock_share = sub {
112             $_[ 0 ]->close; return 1;
113             };
114              
115             my $_write_shmfile = sub {
116             my ($self, $lock_file, $content) = @_; my $wtr;
117              
118             try { $wtr = $self->_shmfile->assert_open( 'w', $self->mode ) }
119             catch { $_unlock_share->( $lock_file ); throw $_ };
120              
121             try { $wtr->print( nfreeze $content ) }
122             catch { $wtr->delete; $_unlock_share->( $lock_file ); throw $_ };
123              
124             $wtr->close; $_unlock_share->( $lock_file );
125             return 1;
126             };
127              
128             my $_reset = sub {
129             my ($self, $args) = @_; my $key = $args->{k}; my $pid = $args->{p};
130              
131             my ($lock_file, $shm_content) = $self->$_read_shmfile;
132              
133             my $lock; exists $shm_content->{ $key }
134             and $lock = $shm_content->{ $key }
135             and $lock->{spid} != $pid
136             and $_unlock_share->( $lock_file )
137             and throw 'Lock [_1] set by another process', [ $key ];
138              
139             not delete $shm_content->{ $key } and $_unlock_share->( $lock_file )
140             and throw 'Lock [_1] not set', [ $key ];
141              
142             return $self->$_write_shmfile( $lock_file, $shm_content );
143             };
144              
145             my $_set = sub {
146             my ($self, $args, $now) = @_; my $key = $args->{k}; my $pid = $args->{p};
147              
148             my ($lock_file, $shm_content) = $self->$_read_shmfile( $args->{async} );
149              
150             not $lock_file->have_lock and $_unlock_share->( $lock_file ) and return 0;
151              
152             my $lock; exists $shm_content->{ $key }
153             and $lock = $shm_content->{ $key }
154             and $lock->{timeout}
155             and $now > $lock->{stime} + $lock->{timeout}
156             and $lock = $self->$_expire_lock( $shm_content, $key, $lock );
157              
158             $lock and $_unlock_share->( $lock_file ) and return 0;
159              
160             $shm_content->{ $key }
161             = { spid => $pid, stime => $now, timeout => $args->{t} };
162             $self->$_write_shmfile( $lock_file, $shm_content );
163             $self->log->debug( "Lock ${key} set by ${pid}" );
164             return 1;
165             };
166              
167             # Public methods
168             sub list {
169 6     6 1 158 my $self = shift; my $list = [];
  6         7  
170              
171 6         10 my ($lock_file, $shm_content) = $self->$_read_shmfile; $lock_file->close;
  6         11  
172              
173 6         882 while (my ($key, $info) = each %{ $shm_content }) {
  10         42  
174 4         16 push @{ $list }, { key => $key,
175             pid => $info->{spid},
176             stime => $info->{stime},
177 4         5 timeout => $info->{timeout} };
178             }
179              
180 6         26 return $list;
181             }
182              
183             sub reset {
184 4     4 1 9257 my $self = shift; return $self->$_reset( $self->_get_args( @_ ) );
  4         11  
185             }
186              
187             sub set {
188 4     4 1 3046 my ($self, @args) = @_; return loop_until( $_set )->( $self, @args );
  4         14  
189             }
190              
191             1;
192              
193             __END__
194              
195             =pod
196              
197             =encoding utf-8
198              
199             =head1 Name
200              
201             IPC::SRLock::Fcntl - Set / reset locks using fcntl
202              
203             =head1 Synopsis
204              
205             use IPC::SRLock;
206              
207             my $config = { tempdir => q(path_to_tmp_directory), type => q(fcntl) };
208              
209             my $lock_obj = IPC::SRLock->new( $config );
210              
211             =head1 Description
212              
213             Uses L<Fcntl> to lock access to a disk based file which is
214             read/written in L<Storable> format. This is the default type for
215             L<IPC::SRLock>.
216              
217             =head1 Configuration and Environment
218              
219             This class defines accessors for these attributes:
220              
221             =over 3
222              
223             =item C<lockfile>
224              
225             Path to the file used by fcntl
226              
227             =item C<mode>
228              
229             File mode to use when creating the lock table file. Defaults to 0666
230              
231             =item C<pattern>
232              
233             Regexp used to untaint file names
234              
235             =item C<shmfile>
236              
237             Path to the lock table file
238              
239             =item C<tempdir>
240              
241             Path to the directory where the lock files reside. Defaults to
242             C<File::Spec-E<gt>tmpdir>
243              
244             =item C<umask>
245              
246             The umask to set when creating the lock table file. Defaults to 0
247              
248             =back
249              
250             =head1 Subroutines/Methods
251              
252             =head2 C<BUILDARGS>
253              
254             Extract the L</tempdir> attribute value from the C<config> object
255             if one was supplied
256              
257             =head2 list
258              
259             List the contents of the lock table
260              
261             =head2 _read_shmfile
262              
263             Read the file containing the lock table from disk
264              
265             =head2 reset
266              
267             Delete a lock from the lock table
268              
269             =head2 set
270              
271             Set a lock in the lock table
272              
273             =head2 _write_shmfile
274              
275             Write the lock table to the disk file
276              
277             =head1 Diagnostics
278              
279             None
280              
281             =head1 Dependencies
282              
283             =over 3
284              
285             =item L<File::DataClass>
286              
287             =item L<IPC::SRLock::Base>
288              
289             =item L<Moo>
290              
291             =item L<Storable>
292              
293             =item L<Time::HiRes>
294              
295             =item L<Try::Tiny>
296              
297             =back
298              
299             =head1 Incompatibilities
300              
301             There are no known incompatibilities in this module
302              
303             =head1 Bugs and Limitations
304              
305             There are no known bugs in this module.
306             Please report problems to the address below.
307             Patches are welcome
308              
309             =head1 Author
310              
311             Peter Flanigan, C<< <pjfl@cpan.org> >>
312              
313             =head1 License and Copyright
314              
315             Copyright (c) 2016 Peter Flanigan. All rights reserved
316              
317             This program is free software; you can redistribute it and/or modify it
318             under the same terms as Perl itself. See L<perlartistic>
319              
320             This program is distributed in the hope that it will be useful,
321             but WITHOUT WARRANTY; without even the implied warranty of
322             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
323              
324             =cut
325              
326             # Local Variables:
327             # mode: perl
328             # tab-width: 3
329             # End: