File Coverage

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


line stmt bran cond sub pod time code
1             package IPC::SRLock::Fcntl;
2              
3 1     1   546 use namespace::autoclean;
  1         1  
  1         8  
4              
5 1     1   76 use English qw( -no_match_vars );
  1         2  
  1         6  
6 1     1   347 use File::DataClass::Constants qw( LOCK_BLOCKING LOCK_NONBLOCKING );
  1         1  
  1         80  
7 1         12 use File::DataClass::Types qw( Directory NonEmptySimpleStr
8 1     1   7 OctalNum Path PositiveInt RegexpRef );
  1         1  
9 1     1   824 use File::Spec;
  1         1  
  1         29  
10 1         5 use IPC::SRLock::Utils qw( Unspecified hash_from loop_until
11 1     1   4 merge_attributes throw );
  1         2  
12 1     1   968 use Storable qw( nfreeze thaw );
  1         2678  
  1         68  
13 1     1   7 use Try::Tiny;
  1         1  
  1         49  
14 1     1   5 use Moo;
  1         1  
  1         8  
15              
16             extends q(IPC::SRLock::Base);
17              
18             # Attribute constructors
19             my $_build_lockfile = sub {
20 1     1   499 my $self = shift; my $path = $self->_lockfile_name;
  1         5  
21              
22             # uncoverable condition false
23 1   33     11 $path ||= $self->tempdir->catfile( $self->name.'.lck' );
24             # uncoverable branch true
25 1 50       580 $path =~ $self->pattern or throw 'Path [_1] cannot untaint', [ $path ];
26              
27 1         39 return $path;
28             };
29              
30             my $_build__shmfile = sub {
31 2     2   554 my $self = shift; my $path = $self->_shmfile_name;
  2         7  
32              
33             # uncoverable condition false
34 2   66     21 $path ||= $self->tempdir->catfile( $self->name.'.shm' );
35             # uncoverable branch true
36 2 50       896 $path =~ $self->pattern or throw 'Path [_1] cannot untaint', [ $path ];
37              
38 2         48 return $path;
39             };
40              
41             # Public attributes
42             has 'lockfile' => is => 'lazy', isa => Path, coerce => 1,
43             builder => $_build_lockfile;
44              
45             has 'mode' => is => 'ro', isa => OctalNum, coerce => 1, default => '0666';
46              
47             has 'pattern' => is => 'ro', isa => RegexpRef,
48             default => sub { qr{ \A ([ ~:+./\-\\\w]+) \z }msx };
49              
50             has 'tempdir' => is => 'ro', isa => Directory, coerce => 1,
51             default => sub { File::Spec->tmpdir };
52              
53             has 'umask' => is => 'ro', isa => PositiveInt, default => 0;
54              
55             # Private attributes
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 146 my $self = shift; my $list = [];
  6         9  
170              
171 6         14 my ($lock_file, $shm_content) = $self->$_read_shmfile; $lock_file->close;
  6         15  
172              
173 6         936 while (my ($key, $info) = each %{ $shm_content }) {
  10         38  
174 4         22 push @{ $list }, { key => $key,
175             pid => $info->{spid},
176             stime => $info->{stime},
177 4         6 timeout => $info->{timeout} };
178             }
179              
180 6         36 return $list;
181             }
182              
183             sub reset {
184 4     4 1 10606 my $self = shift; return $self->$_reset( $self->_get_args( @_ ) );
  4         19  
185             }
186              
187             sub set {
188 4     4 1 4449 my ($self, @args) = @_; return loop_until( $_set )->( $self, @args );
  4         21  
189             }
190              
191             1;
192              
193             __END__