| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# -*- perl -*- |
|
2
|
|
|
|
|
|
|
# |
|
3
|
|
|
|
|
|
|
# File::NFSLock - bdpO - NFS compatible (safe) locking utility |
|
4
|
|
|
|
|
|
|
# |
|
5
|
|
|
|
|
|
|
# $Id: NFSLock.pm,v 1.29 2018/11/01 14:00:00 bbb Exp $ |
|
6
|
|
|
|
|
|
|
# |
|
7
|
|
|
|
|
|
|
# Copyright (C) 2002, Paul T Seamons |
|
8
|
|
|
|
|
|
|
# paul@seamons.com |
|
9
|
|
|
|
|
|
|
# http://seamons.com/ |
|
10
|
|
|
|
|
|
|
# |
|
11
|
|
|
|
|
|
|
# Rob B Brown |
|
12
|
|
|
|
|
|
|
# bbb@cpan.org |
|
13
|
|
|
|
|
|
|
# |
|
14
|
|
|
|
|
|
|
# This package may be distributed under the terms of either the |
|
15
|
|
|
|
|
|
|
# GNU General Public License |
|
16
|
|
|
|
|
|
|
# or the |
|
17
|
|
|
|
|
|
|
# Perl Artistic License |
|
18
|
|
|
|
|
|
|
# |
|
19
|
|
|
|
|
|
|
# All rights reserved. |
|
20
|
|
|
|
|
|
|
# |
|
21
|
|
|
|
|
|
|
# Please read the perldoc File::NFSLock |
|
22
|
|
|
|
|
|
|
# |
|
23
|
|
|
|
|
|
|
################################################################ |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
package File::NFSLock; |
|
26
|
|
|
|
|
|
|
|
|
27
|
78
|
|
|
78
|
|
6747738
|
use strict; |
|
|
78
|
|
|
|
|
1022
|
|
|
|
78
|
|
|
|
|
2262
|
|
|
28
|
78
|
|
|
78
|
|
422
|
use warnings; |
|
|
78
|
|
|
|
|
130
|
|
|
|
78
|
|
|
|
|
2247
|
|
|
29
|
|
|
|
|
|
|
|
|
30
|
78
|
|
|
78
|
|
411
|
use Carp qw(croak confess); |
|
|
78
|
|
|
|
|
155
|
|
|
|
78
|
|
|
|
|
4855
|
|
|
31
|
|
|
|
|
|
|
our $errstr; |
|
32
|
78
|
|
|
78
|
|
492
|
use base 'Exporter'; |
|
|
78
|
|
|
|
|
171
|
|
|
|
78
|
|
|
|
|
14084
|
|
|
33
|
|
|
|
|
|
|
our @EXPORT_OK = qw(uncache); |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
our $VERSION = '1.29'; |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
#Get constants, but without the bloat of |
|
38
|
|
|
|
|
|
|
#use Fcntl qw(LOCK_SH LOCK_EX LOCK_NB); |
|
39
|
|
|
|
|
|
|
use constant { |
|
40
|
78
|
|
|
|
|
193793
|
LOCK_SH => 1, |
|
41
|
|
|
|
|
|
|
LOCK_EX => 2, |
|
42
|
|
|
|
|
|
|
LOCK_NB => 4, |
|
43
|
78
|
|
|
78
|
|
570
|
}; |
|
|
78
|
|
|
|
|
158
|
|
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
### Convert lock_type to a number |
|
46
|
|
|
|
|
|
|
our $TYPES = { |
|
47
|
|
|
|
|
|
|
BLOCKING => LOCK_EX, |
|
48
|
|
|
|
|
|
|
BL => LOCK_EX, |
|
49
|
|
|
|
|
|
|
EXCLUSIVE => LOCK_EX, |
|
50
|
|
|
|
|
|
|
EX => LOCK_EX, |
|
51
|
|
|
|
|
|
|
NONBLOCKING => LOCK_EX | LOCK_NB, |
|
52
|
|
|
|
|
|
|
NB => LOCK_EX | LOCK_NB, |
|
53
|
|
|
|
|
|
|
SHARED => LOCK_SH, |
|
54
|
|
|
|
|
|
|
SH => LOCK_SH, |
|
55
|
|
|
|
|
|
|
}; |
|
56
|
|
|
|
|
|
|
our $LOCK_EXTENSION = '.NFSLock'; # customizable extension |
|
57
|
|
|
|
|
|
|
our $HOSTNAME = undef; |
|
58
|
|
|
|
|
|
|
our $SHARE_BIT = 1; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
my $graceful_sig = sub { |
|
63
|
|
|
|
|
|
|
print STDERR "Received SIG$_[0]\n" if @_; |
|
64
|
|
|
|
|
|
|
# Perl's exit should safely DESTROY any objects |
|
65
|
|
|
|
|
|
|
# still "alive" before calling the real _exit(). |
|
66
|
|
|
|
|
|
|
exit 1; |
|
67
|
|
|
|
|
|
|
}; |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
our @CATCH_SIGS = qw(TERM INT); |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub new { |
|
72
|
1167
|
|
|
1167
|
0
|
82523906
|
$errstr = undef; |
|
73
|
|
|
|
|
|
|
|
|
74
|
1167
|
|
|
|
|
3855
|
my $type = shift; |
|
75
|
1167
|
|
50
|
|
|
9753
|
my $class = ref($type) || $type || __PACKAGE__; |
|
76
|
1167
|
|
|
|
|
3211
|
my $self = {}; |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
### allow for arguments by hash ref or serially |
|
79
|
1167
|
100
|
66
|
|
|
7406
|
if( @_ && ref $_[0] ){ |
|
80
|
1134
|
|
|
|
|
2708
|
$self = shift; |
|
81
|
|
|
|
|
|
|
}else{ |
|
82
|
33
|
|
|
|
|
287
|
$self->{file} = shift; |
|
83
|
33
|
|
|
|
|
252
|
$self->{lock_type} = shift; |
|
84
|
33
|
|
|
|
|
196
|
$self->{blocking_timeout} = shift; |
|
85
|
33
|
|
|
|
|
185
|
$self->{stale_lock_timeout} = shift; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
1167
|
|
50
|
|
|
3755
|
$self->{file} ||= ""; |
|
88
|
1167
|
|
50
|
|
|
3723
|
$self->{lock_type} ||= 0; |
|
89
|
1167
|
|
100
|
|
|
5502
|
$self->{blocking_timeout} ||= 0; |
|
90
|
1167
|
|
100
|
|
|
6019
|
$self->{stale_lock_timeout} ||= 0; |
|
91
|
1167
|
|
|
|
|
4745
|
$self->{lock_pid} = $$; |
|
92
|
1167
|
|
|
|
|
5443
|
$self->{unlocked} = 1; |
|
93
|
1167
|
|
|
|
|
4398
|
foreach my $signal (@CATCH_SIGS) { |
|
94
|
2334
|
100
|
66
|
|
|
10171
|
if (!$SIG{$signal} || |
|
95
|
|
|
|
|
|
|
$SIG{$signal} eq "DEFAULT") { |
|
96
|
2246
|
|
|
|
|
34310
|
$SIG{$signal} = $graceful_sig; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
### force lock_type to be numerical |
|
101
|
1167
|
50
|
33
|
|
|
13992
|
if( $self->{lock_type} && |
|
|
|
|
33
|
|
|
|
|
|
102
|
|
|
|
|
|
|
$self->{lock_type} !~ /^\d+/ && |
|
103
|
|
|
|
|
|
|
exists $TYPES->{$self->{lock_type}} ){ |
|
104
|
0
|
|
|
|
|
0
|
$self->{lock_type} = $TYPES->{$self->{lock_type}}; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
### need the hostname |
|
108
|
1167
|
100
|
|
|
|
3173
|
if( !$HOSTNAME ){ |
|
109
|
68
|
|
|
|
|
74569
|
require Sys::Hostname; |
|
110
|
68
|
|
|
|
|
118772
|
$HOSTNAME = Sys::Hostname::hostname(); |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
### quick usage check |
|
114
|
|
|
|
|
|
|
croak ($errstr = "Usage: my \$f = $class->new('/pathtofile/file',\n" |
|
115
|
|
|
|
|
|
|
."'BLOCKING|EXCLUSIVE|NONBLOCKING|SHARED', [blocking_timeout, stale_lock_timeout]);\n" |
|
116
|
|
|
|
|
|
|
."(You passed \"$self->{file}\" and \"$self->{lock_type}\")") |
|
117
|
1167
|
50
|
|
|
|
4462
|
unless length($self->{file}); |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
croak ($errstr = "Unrecognized lock_type operation setting [$self->{lock_type}]") |
|
120
|
1167
|
50
|
33
|
|
|
8829
|
unless $self->{lock_type} && $self->{lock_type} =~ /^\d+$/; |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
### Input syntax checking passed, ready to bless |
|
123
|
1167
|
|
|
|
|
3037
|
bless $self, $class; |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
### choose a random filename |
|
126
|
1167
|
|
|
|
|
3511
|
$self->{rand_file} = rand_file( $self->{file} ); |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
### choose the lock filename |
|
129
|
1167
|
|
|
|
|
4640
|
$self->{lock_file} = $self->{file} . $LOCK_EXTENSION; |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
my $quit_time = $self->{blocking_timeout} && |
|
132
|
|
|
|
|
|
|
!($self->{lock_type} & LOCK_NB) ? |
|
133
|
1167
|
100
|
66
|
|
|
4522
|
time() + $self->{blocking_timeout} : 0; |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
### remove an old lockfile if it is older than the stale_timeout |
|
136
|
1167
|
50
|
100
|
|
|
22996
|
if( -e $self->{lock_file} && |
|
|
|
|
66
|
|
|
|
|
|
137
|
|
|
|
|
|
|
$self->{stale_lock_timeout} > 0 && |
|
138
|
|
|
|
|
|
|
time() - (stat _)[9] > $self->{stale_lock_timeout} ){ |
|
139
|
0
|
|
|
|
|
0
|
unlink $self->{lock_file}; |
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
|
|
142
|
1167
|
|
|
|
|
2909
|
while (1) { |
|
143
|
|
|
|
|
|
|
### open the temporary file |
|
144
|
1373
|
50
|
|
|
|
6039
|
$self->create_magic |
|
145
|
|
|
|
|
|
|
or return undef; |
|
146
|
|
|
|
|
|
|
|
|
147
|
1373
|
100
|
|
|
|
5331
|
if ( $self->{lock_type} & LOCK_EX ) { |
|
|
|
50
|
|
|
|
|
|
|
148
|
1344
|
100
|
|
|
|
4291
|
last if $self->do_lock; |
|
149
|
|
|
|
|
|
|
} elsif ( $self->{lock_type} & LOCK_SH ) { |
|
150
|
29
|
100
|
|
|
|
125
|
last if $self->do_lock_shared; |
|
151
|
|
|
|
|
|
|
} else { |
|
152
|
0
|
|
|
|
|
0
|
$errstr = "Unknown lock_type [$self->{lock_type}]"; |
|
153
|
0
|
|
|
|
|
0
|
return undef; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
### Lock failed! |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
### I know this may be a race condition, but it's okay. It is just a |
|
159
|
|
|
|
|
|
|
### stab in the dark to possibly find long dead processes. |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
### If lock exists and is readable, see who is mooching on the lock |
|
162
|
|
|
|
|
|
|
|
|
163
|
218
|
|
|
|
|
572
|
my $fh; |
|
164
|
218
|
100
|
100
|
|
|
10711
|
if ( -e $self->{lock_file} && |
|
165
|
|
|
|
|
|
|
open ($fh,'+<', $self->{lock_file}) ){ |
|
166
|
|
|
|
|
|
|
|
|
167
|
166
|
|
|
|
|
736
|
my @mine = (); |
|
168
|
166
|
|
|
|
|
407
|
my @them = (); |
|
169
|
166
|
|
|
|
|
385
|
my @dead = (); |
|
170
|
|
|
|
|
|
|
|
|
171
|
166
|
|
|
|
|
858
|
my $has_lock_exclusive = !((stat _)[2] & $SHARE_BIT); |
|
172
|
166
|
|
|
|
|
584
|
my $try_lock_exclusive = !($self->{lock_type} & LOCK_SH); |
|
173
|
|
|
|
|
|
|
|
|
174
|
166
|
|
|
|
|
2850
|
while(defined(my $line=<$fh>)){ |
|
175
|
166
|
50
|
|
|
|
4476
|
if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { |
|
176
|
166
|
|
|
|
|
1373
|
my $pid = $1; |
|
177
|
166
|
100
|
|
|
|
2770
|
if ($pid == $$) { # This is me. |
|
|
|
100
|
|
|
|
|
|
|
178
|
1
|
|
|
|
|
13
|
push @mine, $line; |
|
179
|
|
|
|
|
|
|
}elsif(kill 0, $pid) { # Still running on this host. |
|
180
|
163
|
|
|
|
|
2328
|
push @them, $line; |
|
181
|
|
|
|
|
|
|
}else{ # Finished running on this host. |
|
182
|
2
|
|
|
|
|
29
|
push @dead, $line; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
} else { # Running on another host, so |
|
185
|
0
|
|
|
|
|
0
|
push @them, $line; # assume it is still running. |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
### If there was at least one stale lock discovered... |
|
190
|
166
|
100
|
|
|
|
881
|
if (@dead) { |
|
191
|
|
|
|
|
|
|
# Lock lock_file to avoid a race condition. |
|
192
|
2
|
|
|
|
|
18
|
local $LOCK_EXTENSION = ".shared"; |
|
193
|
|
|
|
|
|
|
my $lock = new File::NFSLock { |
|
194
|
|
|
|
|
|
|
file => $self->{lock_file}, |
|
195
|
2
|
|
|
|
|
66
|
lock_type => LOCK_EX, |
|
196
|
|
|
|
|
|
|
blocking_timeout => 62, |
|
197
|
|
|
|
|
|
|
stale_lock_timeout => 60, |
|
198
|
|
|
|
|
|
|
}; |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
### Rescan in case lock contents were modified between time stale lock |
|
201
|
|
|
|
|
|
|
### was discovered and lockfile lock was acquired. |
|
202
|
2
|
|
|
|
|
23
|
seek ($fh, 0, 0); |
|
203
|
2
|
|
|
|
|
14
|
my $content = ''; |
|
204
|
2
|
|
|
|
|
26
|
while(defined(my $line=<$fh>)){ |
|
205
|
2
|
50
|
|
|
|
73
|
if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { |
|
206
|
2
|
|
|
|
|
10
|
my $pid = $1; |
|
207
|
2
|
50
|
|
|
|
45
|
next if (!kill 0, $pid); # Skip dead locks from this host |
|
208
|
|
|
|
|
|
|
} |
|
209
|
0
|
|
|
|
|
0
|
$content .= $line; # Save valid locks |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
### Save any valid locks or wipe file. |
|
213
|
2
|
50
|
|
|
|
12
|
if( length($content) ){ |
|
214
|
0
|
|
|
|
|
0
|
seek $fh, 0, 0; |
|
215
|
0
|
|
|
|
|
0
|
print $fh $content; |
|
216
|
0
|
|
|
|
|
0
|
truncate $fh, length($content); |
|
217
|
0
|
|
|
|
|
0
|
close $fh; |
|
218
|
|
|
|
|
|
|
}else{ |
|
219
|
2
|
|
|
|
|
18
|
close $fh; |
|
220
|
2
|
|
|
|
|
96
|
unlink $self->{lock_file}; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
### No "dead" or stale locks found. |
|
224
|
|
|
|
|
|
|
} else { |
|
225
|
164
|
|
|
|
|
2903
|
close $fh; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
### If attempting to acquire the same type of lock |
|
229
|
|
|
|
|
|
|
### that it is already locked with, and I've already |
|
230
|
|
|
|
|
|
|
### locked it myself, then it is safe to lock again. |
|
231
|
|
|
|
|
|
|
### Just kick out successfully without really locking. |
|
232
|
|
|
|
|
|
|
### Assumes locks will be released in the reverse |
|
233
|
|
|
|
|
|
|
### order from how they were established. |
|
234
|
166
|
100
|
100
|
|
|
1695
|
if ($try_lock_exclusive eq $has_lock_exclusive && @mine){ |
|
235
|
1
|
|
|
|
|
8
|
return $self; |
|
236
|
|
|
|
|
|
|
} |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
### If non-blocking, then kick out now. |
|
240
|
|
|
|
|
|
|
### ($errstr might already be set to the reason.) |
|
241
|
217
|
100
|
|
|
|
1056
|
if ($self->{lock_type} & LOCK_NB) { |
|
242
|
11
|
|
50
|
|
|
139
|
$errstr ||= "NONBLOCKING lock failed!"; |
|
243
|
11
|
|
|
|
|
85
|
return undef; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
### wait a moment |
|
247
|
206
|
|
|
|
|
206047494
|
sleep(1); |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
### but don't wait past the time out |
|
250
|
206
|
50
|
66
|
|
|
5596
|
if( $quit_time && (time > $quit_time) ){ |
|
251
|
0
|
|
|
|
|
0
|
$errstr = "Timed out waiting for blocking lock"; |
|
252
|
0
|
|
|
|
|
0
|
return undef; |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# BLOCKING Lock, So Keep Trying |
|
256
|
|
|
|
|
|
|
} |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
### clear up the NFS cache |
|
259
|
1155
|
|
|
|
|
5762
|
$self->uncache; |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
### Yes, the lock has been acquired. |
|
262
|
1155
|
|
|
|
|
4827
|
delete $self->{unlocked}; |
|
263
|
|
|
|
|
|
|
|
|
264
|
1155
|
|
|
|
|
4130
|
return $self; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
sub DESTROY { |
|
268
|
1167
|
|
|
1167
|
|
36093930
|
shift()->unlock(); |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
sub unlock ($) { |
|
272
|
1187
|
|
|
1187
|
1
|
100015469
|
my $self = shift; |
|
273
|
1187
|
100
|
|
|
|
3656
|
if (!$self->{unlocked}) { |
|
274
|
1155
|
50
|
|
|
|
19555
|
unlink( $self->{rand_file} ) if -e $self->{rand_file}; |
|
275
|
1155
|
100
|
|
|
|
4797
|
if( $self->{lock_type} & LOCK_SH ){ |
|
276
|
33
|
|
|
|
|
355
|
$self->do_unlock_shared; |
|
277
|
|
|
|
|
|
|
}else{ |
|
278
|
1122
|
|
|
|
|
3230
|
$self->do_unlock; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
1155
|
|
|
|
|
4727
|
$self->{unlocked} = 1; |
|
281
|
1155
|
|
|
|
|
3383
|
foreach my $signal (@CATCH_SIGS) { |
|
282
|
2310
|
100
|
66
|
|
|
16101
|
if ($SIG{$signal} && |
|
283
|
|
|
|
|
|
|
($SIG{$signal} eq $graceful_sig)) { |
|
284
|
|
|
|
|
|
|
# Revert handler back to how it used to be. |
|
285
|
|
|
|
|
|
|
# Unfortunately, this will restore the |
|
286
|
|
|
|
|
|
|
# handler back even if there are other |
|
287
|
|
|
|
|
|
|
# locks still in tact, but for most cases, |
|
288
|
|
|
|
|
|
|
# it will still be an improvement. |
|
289
|
2240
|
|
|
|
|
32439
|
delete $SIG{$signal}; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
} |
|
293
|
1187
|
|
|
|
|
17449
|
return 1; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
###----------------------------------------------------------------### |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# concepts for these routines were taken from Mail::Box which |
|
299
|
|
|
|
|
|
|
# took the concepts from Mail::Folder |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub rand_file ($) { |
|
303
|
2322
|
|
|
2322
|
0
|
4179
|
my $file = shift; |
|
304
|
2322
|
|
|
|
|
17810
|
"$file.tmp.". time()%10000 .'.'. $$ .'.'. int(rand()*10000); |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub create_magic ($;$) { |
|
308
|
1401
|
|
|
1401
|
0
|
2965
|
$errstr = undef; |
|
309
|
1401
|
|
|
|
|
2606
|
my $self = shift; |
|
310
|
1401
|
|
66
|
|
|
7406
|
my $append_file = shift || $self->{rand_file}; |
|
311
|
1401
|
|
66
|
|
|
11611
|
$self->{lock_line} ||= "$HOSTNAME $self->{lock_pid} ".time()." ".int(rand()*10000)."\n"; |
|
312
|
1401
|
50
|
|
|
|
106237
|
open (my $fh,'>>', $append_file) or do { $errstr = "Couldn't open \"$append_file\" [$!]"; return undef; }; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
313
|
1401
|
|
|
|
|
12078
|
print $fh $self->{lock_line}; |
|
314
|
1401
|
|
|
|
|
45362
|
close $fh; |
|
315
|
1401
|
|
|
|
|
10581
|
return 1; |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
sub do_lock { |
|
319
|
1344
|
|
|
1344
|
0
|
2645
|
$errstr = undef; |
|
320
|
1344
|
|
|
|
|
2573
|
my $self = shift; |
|
321
|
1344
|
|
|
|
|
2628
|
my $lock_file = $self->{lock_file}; |
|
322
|
1344
|
|
|
|
|
2247
|
my $rand_file = $self->{rand_file}; |
|
323
|
1344
|
|
|
|
|
2125
|
my $chmod = 0600; |
|
324
|
1344
|
50
|
|
|
|
23139
|
chmod( $chmod, $rand_file) |
|
325
|
|
|
|
|
|
|
|| die "I need ability to chmod files to adequatetly perform locking"; |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
### try a hard link, if it worked |
|
328
|
|
|
|
|
|
|
### two files are pointing to $rand_file |
|
329
|
1344
|
|
66
|
|
|
46148
|
my $success = link( $rand_file, $lock_file ) |
|
330
|
|
|
|
|
|
|
&& -e $rand_file && (stat _)[3] == 2; |
|
331
|
1344
|
|
|
|
|
38895
|
unlink $rand_file; |
|
332
|
|
|
|
|
|
|
|
|
333
|
1344
|
|
|
|
|
8905
|
return $success; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub do_lock_shared { |
|
337
|
29
|
|
|
29
|
0
|
56
|
$errstr = undef; |
|
338
|
29
|
|
|
|
|
61
|
my $self = shift; |
|
339
|
29
|
|
|
|
|
66
|
my $lock_file = $self->{lock_file}; |
|
340
|
29
|
|
|
|
|
78
|
my $rand_file = $self->{rand_file}; |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
### chmod local file to make sure we know before |
|
343
|
29
|
|
|
|
|
46
|
my $chmod = 0600; |
|
344
|
29
|
|
|
|
|
54
|
$chmod |= $SHARE_BIT; |
|
345
|
29
|
50
|
|
|
|
590
|
chmod( $chmod, $rand_file) |
|
346
|
|
|
|
|
|
|
|| die "I need ability to chmod files to adequatetly perform locking"; |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
### lock the locking process |
|
349
|
29
|
|
|
|
|
389
|
local $LOCK_EXTENSION = ".shared"; |
|
350
|
29
|
|
|
|
|
658
|
my $lock = new File::NFSLock { |
|
351
|
|
|
|
|
|
|
file => $lock_file, |
|
352
|
|
|
|
|
|
|
lock_type => LOCK_EX, |
|
353
|
|
|
|
|
|
|
blocking_timeout => 62, |
|
354
|
|
|
|
|
|
|
stale_lock_timeout => 60, |
|
355
|
|
|
|
|
|
|
}; |
|
356
|
|
|
|
|
|
|
# The ".shared" lock will be released as this status |
|
357
|
|
|
|
|
|
|
# is returned, whether or not the status is successful. |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
### If I didn't have exclusive and the shared bit is not |
|
360
|
|
|
|
|
|
|
### set, I have failed |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
### Try to create $lock_file from the special |
|
363
|
|
|
|
|
|
|
### file with the magic $SHARE_BIT set. |
|
364
|
29
|
|
|
|
|
409
|
my $success = link( $rand_file, $lock_file); |
|
365
|
29
|
|
|
|
|
1201
|
unlink $rand_file; |
|
366
|
29
|
100
|
66
|
|
|
1183
|
if ( !$success && |
|
|
|
100
|
100
|
|
|
|
|
|
367
|
|
|
|
|
|
|
-e $lock_file && |
|
368
|
|
|
|
|
|
|
((stat _)[2] & $SHARE_BIT) != $SHARE_BIT ){ |
|
369
|
|
|
|
|
|
|
|
|
370
|
2
|
|
|
|
|
15
|
$errstr = 'Exclusive lock exists.'; |
|
371
|
2
|
|
|
|
|
17
|
return undef; |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
} elsif ( !$success ) { |
|
374
|
|
|
|
|
|
|
### Shared lock exists, append my lock |
|
375
|
20
|
|
|
|
|
159
|
$self->create_magic ($self->{lock_file}); |
|
376
|
|
|
|
|
|
|
} |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# Success |
|
379
|
27
|
|
|
|
|
212
|
return 1; |
|
380
|
|
|
|
|
|
|
} |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub do_unlock ($) { |
|
383
|
1122
|
|
|
1122
|
0
|
42681
|
return unlink shift->{lock_file}; |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub do_unlock_shared ($) { |
|
387
|
33
|
|
|
33
|
0
|
223
|
$errstr = undef; |
|
388
|
33
|
|
|
|
|
175
|
my $self = shift; |
|
389
|
33
|
|
|
|
|
189
|
my $lock_file = $self->{lock_file}; |
|
390
|
33
|
|
|
|
|
201
|
my $lock_line = $self->{lock_line}; |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
### lock the locking process |
|
393
|
33
|
|
|
|
|
630
|
local $LOCK_EXTENSION = '.shared'; |
|
394
|
33
|
|
|
|
|
903
|
my $lock = new File::NFSLock ($lock_file,LOCK_EX,62,60); |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
### get the handle on the lock file |
|
397
|
33
|
|
|
|
|
168
|
my $fh; |
|
398
|
33
|
50
|
|
|
|
1387
|
if( ! open ($fh,'+<', $lock_file) ){ |
|
399
|
0
|
0
|
|
|
|
0
|
if( ! -e $lock_file ){ |
|
400
|
0
|
|
|
|
|
0
|
return 1; |
|
401
|
|
|
|
|
|
|
}else{ |
|
402
|
0
|
|
|
|
|
0
|
die "Could not open for writing shared lock file $lock_file ($!)"; |
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
} |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
### read existing file |
|
407
|
33
|
|
|
|
|
175
|
my $content = ''; |
|
408
|
33
|
|
|
|
|
823
|
while(defined(my $line=<$fh>)){ |
|
409
|
251
|
100
|
|
|
|
814
|
next if $line eq $lock_line; |
|
410
|
218
|
|
|
|
|
931
|
$content .= $line; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
### other shared locks exist |
|
414
|
33
|
100
|
|
|
|
296
|
if( length($content) ){ |
|
415
|
28
|
|
|
|
|
281
|
seek $fh, 0, 0; |
|
416
|
28
|
|
|
|
|
224
|
print $fh $content; |
|
417
|
28
|
|
|
|
|
1540
|
truncate $fh, length($content); |
|
418
|
28
|
|
|
|
|
905
|
close $fh; |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
### only I exist |
|
421
|
|
|
|
|
|
|
}else{ |
|
422
|
5
|
|
|
|
|
60
|
close $fh; |
|
423
|
5
|
|
|
|
|
503
|
unlink $lock_file; |
|
424
|
|
|
|
|
|
|
} |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
sub uncache ($;$) { |
|
429
|
|
|
|
|
|
|
# allow as method call |
|
430
|
1155
|
|
|
1155
|
1
|
2309
|
my $file = pop; |
|
431
|
1155
|
50
|
|
|
|
4194
|
ref $file && ($file = $file->{file}); |
|
432
|
1155
|
|
|
|
|
2550
|
my $rand_file = rand_file( $file ); |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
### hard link to the actual file which will bring it up to date |
|
435
|
1155
|
|
66
|
|
|
64325
|
return ( link( $file, $rand_file) && unlink($rand_file) ); |
|
436
|
|
|
|
|
|
|
} |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
sub newpid { |
|
439
|
12
|
|
|
12
|
1
|
8014559
|
my $self = shift; |
|
440
|
|
|
|
|
|
|
# Detect if this is the parent or the child |
|
441
|
12
|
100
|
|
|
|
506
|
if ($self->{lock_pid} == $$) { |
|
442
|
|
|
|
|
|
|
# This is the parent |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# Must wait for child to call newpid before processing. |
|
445
|
|
|
|
|
|
|
# A little patience for the child to call newpid |
|
446
|
4
|
|
|
|
|
37
|
my $patience = time + 10; |
|
447
|
4
|
|
|
|
|
75
|
while (time < $patience) { |
|
448
|
46
|
100
|
|
|
|
2482
|
if (rename("$self->{lock_file}.fork",$self->{rand_file})) { |
|
449
|
|
|
|
|
|
|
# Child finished its newpid call. |
|
450
|
|
|
|
|
|
|
# Wipe the signal file. |
|
451
|
4
|
|
|
|
|
255
|
unlink $self->{rand_file}; |
|
452
|
4
|
|
|
|
|
71
|
last; |
|
453
|
|
|
|
|
|
|
} |
|
454
|
|
|
|
|
|
|
# Brief pause before checking again |
|
455
|
|
|
|
|
|
|
# to avoid intensive IO across NFS. |
|
456
|
42
|
|
|
|
|
4210402
|
select(undef,undef,undef,0.1); |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# Child finished running newpid() and acquired shared lock |
|
460
|
|
|
|
|
|
|
# So now we're safe to continue without risk of |
|
461
|
|
|
|
|
|
|
# blowing away the lock prematurely. |
|
462
|
4
|
100
|
|
|
|
131
|
unless ( $self->{lock_type} & LOCK_SH ) { |
|
463
|
|
|
|
|
|
|
# If it's not already a SHared lock, then |
|
464
|
|
|
|
|
|
|
# just switch it from EXclusive to SHared |
|
465
|
|
|
|
|
|
|
# from this process's point of view. |
|
466
|
|
|
|
|
|
|
# Then the child will still hold the lock |
|
467
|
|
|
|
|
|
|
# if the parent releases it first. |
|
468
|
|
|
|
|
|
|
# (Don't chmod the lock file.) |
|
469
|
2
|
|
|
|
|
64
|
$self->{lock_type} |= LOCK_SH; |
|
470
|
|
|
|
|
|
|
} |
|
471
|
|
|
|
|
|
|
} else { |
|
472
|
|
|
|
|
|
|
# This is the new child |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# Fix lock_pid to the new pid. |
|
475
|
8
|
|
|
|
|
171
|
$self->{lock_pid} = $$; |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# We can leave the old lock_line in the lock_file |
|
478
|
|
|
|
|
|
|
# But we need to add the new lock_line for this pid. |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# Clear lock_line to create a fresh one. |
|
481
|
8
|
|
|
|
|
434
|
delete $self->{lock_line}; |
|
482
|
|
|
|
|
|
|
# Append a new lock_line to the lock_file. |
|
483
|
8
|
|
|
|
|
317
|
$self->create_magic($self->{lock_file}); |
|
484
|
|
|
|
|
|
|
|
|
485
|
8
|
100
|
|
|
|
157
|
unless ( $self->{lock_type} & LOCK_SH ) { |
|
486
|
|
|
|
|
|
|
# If it's not already a SHared lock, then |
|
487
|
|
|
|
|
|
|
# just switch it from EXclusive to SHared |
|
488
|
|
|
|
|
|
|
# from this process's point of view. |
|
489
|
|
|
|
|
|
|
# Then the parent will still hold the lock |
|
490
|
|
|
|
|
|
|
# if this child releases it first. |
|
491
|
|
|
|
|
|
|
# (Don't chmod the lock file.) |
|
492
|
4
|
|
|
|
|
56
|
$self->{lock_type} |= LOCK_SH; |
|
493
|
|
|
|
|
|
|
} |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
# Create signal file to notify parent that |
|
496
|
|
|
|
|
|
|
# the lock_line entry has been delegated. |
|
497
|
8
|
|
|
|
|
663
|
open (my $fh, '>', "$self->{lock_file}.fork"); |
|
498
|
8
|
|
|
|
|
222
|
close($fh); |
|
499
|
|
|
|
|
|
|
} |
|
500
|
|
|
|
|
|
|
} |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
sub fork { |
|
503
|
6
|
|
|
6
|
1
|
1838
|
my $self = shift; |
|
504
|
|
|
|
|
|
|
# Store fork response. |
|
505
|
6
|
|
|
|
|
5297
|
my $pid = CORE::fork(); |
|
506
|
6
|
50
|
33
|
|
|
646
|
if (defined $pid and !$self->{unlocked}) { |
|
507
|
|
|
|
|
|
|
# Fork worked and we really have a lock to deal with |
|
508
|
|
|
|
|
|
|
# So upgrade to shared lock across both parent and child |
|
509
|
6
|
|
|
|
|
192
|
$self->newpid; |
|
510
|
|
|
|
|
|
|
} |
|
511
|
|
|
|
|
|
|
# Return original fork response |
|
512
|
6
|
|
|
|
|
187
|
return $pid; |
|
513
|
|
|
|
|
|
|
} |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
1; |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=pod |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head1 NAME |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
File::NFSLock - perl module to do NFS (or not) locking |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
use File::NFSLock qw(uncache); |
|
527
|
|
|
|
|
|
|
use Fcntl qw(LOCK_EX LOCK_NB); |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
my $file = "somefile"; |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
### set up a lock - lasts until object looses scope |
|
532
|
|
|
|
|
|
|
if (my $lock = new File::NFSLock { |
|
533
|
|
|
|
|
|
|
file => $file, |
|
534
|
|
|
|
|
|
|
lock_type => LOCK_EX|LOCK_NB, |
|
535
|
|
|
|
|
|
|
blocking_timeout => 10, # 10 sec |
|
536
|
|
|
|
|
|
|
stale_lock_timeout => 30 * 60, # 30 min |
|
537
|
|
|
|
|
|
|
}) { |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
### OR |
|
540
|
|
|
|
|
|
|
### my $lock = File::NFSLock->new($file,LOCK_EX|LOCK_NB,10,30*60); |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
### do write protected stuff on $file |
|
543
|
|
|
|
|
|
|
### at this point $file is uncached from NFS (most recent) |
|
544
|
|
|
|
|
|
|
open(FILE, "+<$file") || die $!; |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
### or open it any way you like |
|
547
|
|
|
|
|
|
|
### my $fh = IO::File->open( $file, 'w' ) || die $! |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
### update (uncache across NFS) other files |
|
550
|
|
|
|
|
|
|
uncache("someotherfile1"); |
|
551
|
|
|
|
|
|
|
uncache("someotherfile2"); |
|
552
|
|
|
|
|
|
|
# open(FILE2,"someotherfile1"); |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
### unlock it |
|
555
|
|
|
|
|
|
|
$lock->unlock(); |
|
556
|
|
|
|
|
|
|
### OR |
|
557
|
|
|
|
|
|
|
### undef $lock; |
|
558
|
|
|
|
|
|
|
### OR let $lock go out of scope |
|
559
|
|
|
|
|
|
|
}else{ |
|
560
|
|
|
|
|
|
|
die "I couldn't lock the file [$File::NFSLock::errstr]"; |
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
Program based of concept of hard linking of files being atomic across |
|
567
|
|
|
|
|
|
|
NFS. This concept was mentioned in Mail::Box::Locker (which was |
|
568
|
|
|
|
|
|
|
originally presented in Mail::Folder::Maildir). Some routine flow is |
|
569
|
|
|
|
|
|
|
taken from there -- particularly the idea of creating a random local |
|
570
|
|
|
|
|
|
|
file, hard linking a common file to the local file, and then checking |
|
571
|
|
|
|
|
|
|
the nlink status. Some ideologies were not complete (uncache |
|
572
|
|
|
|
|
|
|
mechanism, shared locking) and some coding was even incorrect (wrong |
|
573
|
|
|
|
|
|
|
stat index). File::NFSLock was written to be light, generic, |
|
574
|
|
|
|
|
|
|
and fast. |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
=head1 USAGE |
|
578
|
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
Locking occurs by creating a File::NFSLock object. If the object |
|
580
|
|
|
|
|
|
|
is created successfully, a lock is currently in place and remains in |
|
581
|
|
|
|
|
|
|
place until the lock object goes out of scope (or calls the unlock |
|
582
|
|
|
|
|
|
|
method). |
|
583
|
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
A lock object is created by calling the new method and passing two |
|
585
|
|
|
|
|
|
|
to four parameters in the following manner: |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
my $lock = File::NFSLock->new($file, |
|
588
|
|
|
|
|
|
|
$lock_type, |
|
589
|
|
|
|
|
|
|
$blocking_timeout, |
|
590
|
|
|
|
|
|
|
$stale_lock_timeout, |
|
591
|
|
|
|
|
|
|
); |
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Additionally, parameters may be passed as a hashref: |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
my $lock = File::NFSLock->new({ |
|
596
|
|
|
|
|
|
|
file => $file, |
|
597
|
|
|
|
|
|
|
lock_type => $lock_type, |
|
598
|
|
|
|
|
|
|
blocking_timeout => $blocking_timeout, |
|
599
|
|
|
|
|
|
|
stale_lock_timeout => $stale_lock_timeout, |
|
600
|
|
|
|
|
|
|
}); |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=head1 PARAMETERS |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=over 4 |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=item Parameter 1: file |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
Filename of the file upon which it is anticipated that a write will |
|
609
|
|
|
|
|
|
|
happen to. Locking will provide the most recent version (uncached) |
|
610
|
|
|
|
|
|
|
of this file upon a successful file lock. It is not necessary |
|
611
|
|
|
|
|
|
|
for this file to exist. |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=item Parameter 2: lock_type |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
Lock type must be one of the following: |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
BLOCKING |
|
618
|
|
|
|
|
|
|
BL |
|
619
|
|
|
|
|
|
|
EXCLUSIVE (BLOCKING) |
|
620
|
|
|
|
|
|
|
EX |
|
621
|
|
|
|
|
|
|
NONBLOCKING |
|
622
|
|
|
|
|
|
|
NB |
|
623
|
|
|
|
|
|
|
SHARED |
|
624
|
|
|
|
|
|
|
SH |
|
625
|
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
Or else one or more of the following joined with '|': |
|
627
|
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
Fcntl::LOCK_EX() (BLOCKING) |
|
629
|
|
|
|
|
|
|
Fcntl::LOCK_NB() (NONBLOCKING) |
|
630
|
|
|
|
|
|
|
Fcntl::LOCK_SH() (SHARED) |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
Lock type determines whether the lock will be blocking, non blocking, |
|
633
|
|
|
|
|
|
|
or shared. Blocking locks will wait until other locks are removed |
|
634
|
|
|
|
|
|
|
before the process continues. Non blocking locks will return undef if |
|
635
|
|
|
|
|
|
|
another process currently has the lock. Shared will allow other |
|
636
|
|
|
|
|
|
|
process to do a shared lock at the same time as long as there is not |
|
637
|
|
|
|
|
|
|
already an exclusive lock obtained. |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=item Parameter 3: blocking_timeout (optional) |
|
640
|
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
Timeout is used in conjunction with a blocking timeout. If specified, |
|
642
|
|
|
|
|
|
|
File::NFSLock will block up to the number of seconds specified in |
|
643
|
|
|
|
|
|
|
timeout before returning undef (could not get a lock). |
|
644
|
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item Parameter 4: stale_lock_timeout (optional) |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Timeout is used to see if an existing lock file is older than the stale |
|
649
|
|
|
|
|
|
|
lock timeout. If do_lock fails to get a lock, the modified time is checked |
|
650
|
|
|
|
|
|
|
and do_lock is attempted again. If the stale_lock_timeout is set to low, a |
|
651
|
|
|
|
|
|
|
recursion load could exist so do_lock will only recurse 10 times (this is only |
|
652
|
|
|
|
|
|
|
a problem if the stale_lock_timeout is set too low -- on the order of one or two |
|
653
|
|
|
|
|
|
|
seconds). |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=back |
|
656
|
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
=head1 METHODS |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
After the $lock object is instantiated with new, |
|
660
|
|
|
|
|
|
|
as outlined above, some methods may be used for |
|
661
|
|
|
|
|
|
|
additional functionality. |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
=head2 unlock |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
$lock->unlock; |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
This method may be used to explicitly release a lock |
|
668
|
|
|
|
|
|
|
that is acquired. In most cases, it is not necessary |
|
669
|
|
|
|
|
|
|
to call unlock directly since it will implicitly be |
|
670
|
|
|
|
|
|
|
called when the object leaves whatever scope it is in. |
|
671
|
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=head2 uncache |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
$lock->uncache; |
|
675
|
|
|
|
|
|
|
$lock->uncache("otherfile1"); |
|
676
|
|
|
|
|
|
|
uncache("otherfile2"); |
|
677
|
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
This method is used to freshen up the contents of a |
|
679
|
|
|
|
|
|
|
file across NFS, ignoring what is contained in the |
|
680
|
|
|
|
|
|
|
NFS client cache. It is always called from within |
|
681
|
|
|
|
|
|
|
the new constructor on the file that the lock is |
|
682
|
|
|
|
|
|
|
being attempted. uncache may be used as either an |
|
683
|
|
|
|
|
|
|
object method or as a stand alone subroutine. |
|
684
|
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=head2 fork |
|
686
|
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
my $pid = $lock->fork; |
|
688
|
|
|
|
|
|
|
if (!defined $pid) { |
|
689
|
|
|
|
|
|
|
# Fork Failed |
|
690
|
|
|
|
|
|
|
} elsif ($pid) { |
|
691
|
|
|
|
|
|
|
# Parent ... |
|
692
|
|
|
|
|
|
|
} else { |
|
693
|
|
|
|
|
|
|
# Child ... |
|
694
|
|
|
|
|
|
|
} |
|
695
|
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
fork() is a convenience method that acts just like the normal |
|
697
|
|
|
|
|
|
|
CORE::fork() except it safely ensures the lock is retained |
|
698
|
|
|
|
|
|
|
within both parent and child processes. WITHOUT this, then when |
|
699
|
|
|
|
|
|
|
either the parent or child process releases the lock, then the |
|
700
|
|
|
|
|
|
|
entire lock will be lost, allowing external processes to |
|
701
|
|
|
|
|
|
|
re-acquire a lock on the same file, even if the other process |
|
702
|
|
|
|
|
|
|
still has the lock object in scope. This can cause corruption |
|
703
|
|
|
|
|
|
|
since both processes might think they have exclusive access to |
|
704
|
|
|
|
|
|
|
the file. |
|
705
|
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
=head2 newpid |
|
707
|
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
my $pid = fork; |
|
709
|
|
|
|
|
|
|
if (!defined $pid) { |
|
710
|
|
|
|
|
|
|
# Fork Failed |
|
711
|
|
|
|
|
|
|
} elsif ($pid) { |
|
712
|
|
|
|
|
|
|
$lock->newpid; |
|
713
|
|
|
|
|
|
|
# Parent ... |
|
714
|
|
|
|
|
|
|
} else { |
|
715
|
|
|
|
|
|
|
$lock->newpid; |
|
716
|
|
|
|
|
|
|
# Child ... |
|
717
|
|
|
|
|
|
|
} |
|
718
|
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
The newpid() synopsis shown above is equivalent to the |
|
720
|
|
|
|
|
|
|
one used for the fork() method, but it's not intended |
|
721
|
|
|
|
|
|
|
to be called directly. It is called internally by the |
|
722
|
|
|
|
|
|
|
fork() method. To be safe, it is recommended to use |
|
723
|
|
|
|
|
|
|
$lock->fork() from now on. |
|
724
|
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
=head1 FAILURE |
|
726
|
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
On failure, a global variable, $File::NFSLock::errstr, should be set and should |
|
728
|
|
|
|
|
|
|
contain the cause for the failure to get a lock. Useful primarily for debugging. |
|
729
|
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
=head1 LOCK_EXTENSION |
|
731
|
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
By default File::NFSLock will use a lock file extension of ".NFSLock". This is |
|
733
|
|
|
|
|
|
|
in a global variable $File::NFSLock::LOCK_EXTENSION that may be changed to |
|
734
|
|
|
|
|
|
|
suit other purposes (such as compatibility in mail systems). |
|
735
|
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
=head1 REPO |
|
737
|
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
The source is now on github: |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
git clone https://github.com/hookbot/File-NFSLock |
|
741
|
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
=head1 BUGS |
|
743
|
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
If you spot anything, please submit a pull request on |
|
745
|
|
|
|
|
|
|
github and/or submit a ticket with RT: |
|
746
|
|
|
|
|
|
|
https://rt.cpan.org/Dist/Display.html?Queue=File-NFSLock |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=head2 FIFO |
|
749
|
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
Locks are not necessarily obtained on a first come first serve basis. |
|
751
|
|
|
|
|
|
|
Not only does this not seem fair to new processes trying to obtain a lock, |
|
752
|
|
|
|
|
|
|
but it may cause a process starvation condition on heavily locked files. |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=head2 DIRECTORIES |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
Locks cannot be obtained on directory nodes, nor can a directory node be |
|
757
|
|
|
|
|
|
|
uncached with the uncache routine because hard links do not work with |
|
758
|
|
|
|
|
|
|
directory nodes. Some other algorithm might be used to uncache a |
|
759
|
|
|
|
|
|
|
directory, but I am unaware of the best way to do it. The biggest use I |
|
760
|
|
|
|
|
|
|
can see would be to avoid NFS cache of directory modified and last accessed |
|
761
|
|
|
|
|
|
|
timestamps. |
|
762
|
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=head1 INSTALL |
|
764
|
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
Download and extract tarball before running |
|
766
|
|
|
|
|
|
|
these commands in its base directory: |
|
767
|
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
perl Makefile.PL |
|
769
|
|
|
|
|
|
|
make |
|
770
|
|
|
|
|
|
|
make test |
|
771
|
|
|
|
|
|
|
make install |
|
772
|
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
For RPM installation, download tarball before |
|
774
|
|
|
|
|
|
|
running these commands in your _topdir: |
|
775
|
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
rpm -ta SOURCES/File-NFSLock-*.tar.gz |
|
777
|
|
|
|
|
|
|
rpm -ih RPMS/noarch/perl-File-NFSLock-*.rpm |
|
778
|
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=head1 AUTHORS |
|
780
|
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
Paul T Seamons (paul@seamons.com) - Performed majority of the |
|
782
|
|
|
|
|
|
|
programming with copious amounts of input from Rob Brown. |
|
783
|
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
Rob B Brown (bbb@cpan.org) - In addition to helping in the |
|
785
|
|
|
|
|
|
|
programming, Rob Brown provided most of the core testing to make sure |
|
786
|
|
|
|
|
|
|
implementation worked properly. He is now the current maintainer. |
|
787
|
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
Also Mark Overmeer (mark@overmeer.net) - Author of Mail::Box::Locker, |
|
789
|
|
|
|
|
|
|
from which some key concepts for File::NFSLock were taken. |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
Also Kevin Johnson (kjj@pobox.com) - Author of Mail::Folder::Maildir, |
|
792
|
|
|
|
|
|
|
from which Mark Overmeer based Mail::Box::Locker. |
|
793
|
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
795
|
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
Copyright (C) 2001 |
|
797
|
|
|
|
|
|
|
Paul T Seamons |
|
798
|
|
|
|
|
|
|
paul@seamons.com |
|
799
|
|
|
|
|
|
|
http://seamons.com/ |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
Copyright (C) 2002-2018, |
|
802
|
|
|
|
|
|
|
Rob B Brown |
|
803
|
|
|
|
|
|
|
bbb@cpan.org |
|
804
|
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
This package may be distributed under the terms of either the |
|
806
|
|
|
|
|
|
|
GNU General Public License |
|
807
|
|
|
|
|
|
|
or the |
|
808
|
|
|
|
|
|
|
Perl Artistic License |
|
809
|
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
All rights reserved. |
|
811
|
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
=cut |