Filename | /usr/local/lib/perl5/site_perl/File/NFSLock.pm |
Statements | Executed 76198 statements in 417ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
3598 | 5 | 1 | 114ms | 114ms | CORE:unlink (opcode) | File::NFSLock::
1204 | 3 | 2 | 61.4ms | 333ms | new (recurses: max depth 1, inclusive time 2.57ms) | File::NFSLock::
2408 | 3 | 1 | 48.0ms | 48.0ms | CORE:link (opcode) | File::NFSLock::
1217 | 2 | 1 | 47.8ms | 47.8ms | CORE:open (opcode) | File::NFSLock::
3599 | 3 | 1 | 33.2ms | 33.2ms | CORE:ftis (opcode) | File::NFSLock::
1204 | 2 | 1 | 21.0ms | 21.0ms | CORE:chmod (opcode) | File::NFSLock::
1217 | 2 | 1 | 18.7ms | 18.7ms | CORE:close (opcode) | File::NFSLock::
1204 | 1 | 1 | 15.2ms | 83.4ms | create_magic | File::NFSLock::
1191 | 1 | 1 | 14.4ms | 93.0ms | do_lock | File::NFSLock::
1204 | 1 | 1 | 13.7ms | 72.8ms | uncache | File::NFSLock::
1232 | 2 | 2 | 12.7ms | 97.0ms | unlock (recurses: max depth 1, inclusive time 730µs) | File::NFSLock::
1191 | 1 | 1 | 8.89ms | 65.2ms | do_unlock | File::NFSLock::
1204 | 4 | 2 | 2.97ms | 94.1ms | DESTROY | File::NFSLock::
2408 | 2 | 1 | 2.46ms | 2.46ms | CORE:match (opcode) | File::NFSLock::
1204 | 1 | 1 | 1.93ms | 1.93ms | CORE:print (opcode) | File::NFSLock::
1191 | 1 | 1 | 971µs | 971µs | CORE:stat (opcode) | File::NFSLock::
13 | 1 | 1 | 325µs | 5.34ms | do_unlock_shared | File::NFSLock::
13 | 1 | 1 | 266µs | 4.51ms | do_lock_shared | File::NFSLock::
26 | 1 | 1 | 91µs | 91µs | CORE:readline (opcode) | File::NFSLock::
0 | 0 | 0 | 0s | 0s | BEGIN@27 | File::NFSLock::
0 | 0 | 0 | 0s | 0s | BEGIN@28 | File::NFSLock::
0 | 0 | 0 | 0s | 0s | BEGIN@30 | File::NFSLock::
0 | 0 | 0 | 0s | 0s | BEGIN@32 | File::NFSLock::
0 | 0 | 0 | 0s | 0s | BEGIN@39 | File::NFSLock::
0 | 0 | 0 | 0s | 0s | __ANON__[:67] | File::NFSLock::
0 | 0 | 0 | 0s | 0s | fork | File::NFSLock::
0 | 0 | 0 | 0s | 0s | newpid | File::NFSLock::
0 | 0 | 0 | 0s | 0s | rand_file | File::NFSLock::
Line | State ments |
Time on line |
Calls | Time in subs |
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 | use strict; | ||||
28 | use warnings; | ||||
29 | |||||
30 | use Carp qw(croak confess); | ||||
31 | our $errstr; | ||||
32 | use base 'Exporter'; | ||||
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 | LOCK_SH => 1, | ||||
41 | LOCK_EX => 2, | ||||
42 | LOCK_NB => 4, | ||||
43 | }; | ||||
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 | # spent 333ms (61.4+272) within File::NFSLock::new which was called 1204 times, avg 277µs/call:
# 1178 times (60.7ms+269ms) by Sympa::LockedFile::open at line 77 of /usr/local/libexec/sympa/Sympa/LockedFile.pm, avg 280µs/call
# 13 times (415µs+3.07ms) by File::NFSLock::do_unlock_shared at line 394, avg 268µs/call
# 13 times (343µs+-343µs) by File::NFSLock::do_lock_shared at line 350, avg 0s/call | ||||
72 | 1204 | 405µs | $errstr = undef; | ||
73 | |||||
74 | 1204 | 342µs | my $type = shift; | ||
75 | 1204 | 430µs | my $class = ref($type) || $type || __PACKAGE__; | ||
76 | 1204 | 508µs | my $self = {}; | ||
77 | |||||
78 | ### allow for arguments by hash ref or serially | ||||
79 | 1204 | 990µs | if( @_ && ref $_[0] ){ | ||
80 | $self = shift; | ||||
81 | }else{ | ||||
82 | 13 | 9µs | $self->{file} = shift; | ||
83 | 13 | 7µs | $self->{lock_type} = shift; | ||
84 | 13 | 5µs | $self->{blocking_timeout} = shift; | ||
85 | 13 | 5µs | $self->{stale_lock_timeout} = shift; | ||
86 | } | ||||
87 | 1204 | 508µs | $self->{file} ||= ""; | ||
88 | 1204 | 294µs | $self->{lock_type} ||= 0; | ||
89 | 1204 | 269µs | $self->{blocking_timeout} ||= 0; | ||
90 | 1204 | 259µs | $self->{stale_lock_timeout} ||= 0; | ||
91 | 1204 | 1.43ms | $self->{lock_pid} = $$; | ||
92 | 1204 | 1.15ms | $self->{unlocked} = 1; | ||
93 | 1204 | 624µs | foreach my $signal (@CATCH_SIGS) { | ||
94 | 2408 | 2.97ms | if (!$SIG{$signal} || | ||
95 | $SIG{$signal} eq "DEFAULT") { | ||||
96 | $SIG{$signal} = $graceful_sig; | ||||
97 | } | ||||
98 | } | ||||
99 | |||||
100 | ### force lock_type to be numerical | ||||
101 | 1204 | 9.39ms | 1204 | 1.65ms | if( $self->{lock_type} && # spent 1.65ms making 1204 calls to File::NFSLock::CORE:match, avg 1µs/call |
102 | $self->{lock_type} !~ /^\d+/ && | ||||
103 | exists $TYPES->{$self->{lock_type}} ){ | ||||
104 | $self->{lock_type} = $TYPES->{$self->{lock_type}}; | ||||
105 | } | ||||
106 | |||||
107 | ### need the hostname | ||||
108 | 1204 | 372µs | if( !$HOSTNAME ){ | ||
109 | 1 | 3µs | require Sys::Hostname; | ||
110 | 1 | 14µs | 1 | 54µs | $HOSTNAME = Sys::Hostname::hostname(); # spent 54µs making 1 call to 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 | 1204 | 432µs | unless length($self->{file}); | ||
118 | |||||
119 | croak ($errstr = "Unrecognized lock_type operation setting [$self->{lock_type}]") | ||||
120 | 1204 | 2.72ms | 1204 | 808µs | unless $self->{lock_type} && $self->{lock_type} =~ /^\d+$/; # spent 808µs making 1204 calls to File::NFSLock::CORE:match, avg 671ns/call |
121 | |||||
122 | ### Input syntax checking passed, ready to bless | ||||
123 | 1204 | 430µs | bless $self, $class; | ||
124 | |||||
125 | ### choose a random filename | ||||
126 | 1204 | 2.35ms | 1204 | 6.32ms | $self->{rand_file} = rand_file( $self->{file} ); # spent 6.32ms making 1204 calls to Sympa::LockedFile::__ANON__[/usr/local/libexec/sympa/Sympa/LockedFile.pm:49], avg 5µs/call |
127 | |||||
128 | ### choose the lock filename | ||||
129 | 1204 | 1.04ms | $self->{lock_file} = $self->{file} . $LOCK_EXTENSION; | ||
130 | |||||
131 | my $quit_time = $self->{blocking_timeout} && | ||||
132 | !($self->{lock_type} & LOCK_NB) ? | ||||
133 | 1204 | 851µs | time() + $self->{blocking_timeout} : 0; | ||
134 | |||||
135 | ### remove an old lockfile if it is older than the stale_timeout | ||||
136 | 1204 | 13.7ms | 1204 | 11.7ms | if( -e $self->{lock_file} && # spent 11.7ms making 1204 calls to File::NFSLock::CORE:ftis, avg 10µs/call |
137 | $self->{stale_lock_timeout} > 0 && | ||||
138 | time() - (stat _)[9] > $self->{stale_lock_timeout} ){ | ||||
139 | unlink $self->{lock_file}; | ||||
140 | } | ||||
141 | |||||
142 | 1204 | 230µs | while (1) { | ||
143 | ### open the temporary file | ||||
144 | 1204 | 1.89ms | 1204 | 83.4ms | $self->create_magic # spent 83.4ms making 1204 calls to File::NFSLock::create_magic, avg 69µs/call |
145 | or return undef; | ||||
146 | |||||
147 | 1204 | 663µs | if ( $self->{lock_type} & LOCK_EX ) { | ||
148 | 1191 | 2.39ms | 1191 | 93.0ms | last if $self->do_lock; # spent 93.0ms making 1191 calls to File::NFSLock::do_lock, avg 78µs/call |
149 | } elsif ( $self->{lock_type} & LOCK_SH ) { | ||||
150 | 13 | 30µs | 13 | 4.51ms | last if $self->do_lock_shared; # spent 4.51ms making 13 calls to File::NFSLock::do_lock_shared, avg 347µs/call |
151 | } else { | ||||
152 | $errstr = "Unknown lock_type [$self->{lock_type}]"; | ||||
153 | 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 | my $fh; | ||||
164 | if ( -e $self->{lock_file} && | ||||
165 | open ($fh,'+<', $self->{lock_file}) ){ | ||||
166 | |||||
167 | my @mine = (); | ||||
168 | my @them = (); | ||||
169 | my @dead = (); | ||||
170 | |||||
171 | my $has_lock_exclusive = !((stat _)[2] & $SHARE_BIT); | ||||
172 | my $try_lock_exclusive = !($self->{lock_type} & LOCK_SH); | ||||
173 | |||||
174 | while(defined(my $line=<$fh>)){ | ||||
175 | if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { | ||||
176 | my $pid = $1; | ||||
177 | if ($pid == $$) { # This is me. | ||||
178 | push @mine, $line; | ||||
179 | }elsif(kill 0, $pid) { # Still running on this host. | ||||
180 | push @them, $line; | ||||
181 | }else{ # Finished running on this host. | ||||
182 | push @dead, $line; | ||||
183 | } | ||||
184 | } else { # Running on another host, so | ||||
185 | push @them, $line; # assume it is still running. | ||||
186 | } | ||||
187 | } | ||||
188 | |||||
189 | ### If there was at least one stale lock discovered... | ||||
190 | if (@dead) { | ||||
191 | # Lock lock_file to avoid a race condition. | ||||
192 | local $LOCK_EXTENSION = ".shared"; | ||||
193 | my $lock = new File::NFSLock { | ||||
194 | file => $self->{lock_file}, | ||||
195 | 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 | seek ($fh, 0, 0); | ||||
203 | my $content = ''; | ||||
204 | while(defined(my $line=<$fh>)){ | ||||
205 | if ($line =~ /^\Q$HOSTNAME\E (-?\d+) /) { | ||||
206 | my $pid = $1; | ||||
207 | next if (!kill 0, $pid); # Skip dead locks from this host | ||||
208 | } | ||||
209 | $content .= $line; # Save valid locks | ||||
210 | } | ||||
211 | |||||
212 | ### Save any valid locks or wipe file. | ||||
213 | if( length($content) ){ | ||||
214 | seek $fh, 0, 0; | ||||
215 | print $fh $content; | ||||
216 | truncate $fh, length($content); | ||||
217 | close $fh; | ||||
218 | }else{ | ||||
219 | close $fh; | ||||
220 | unlink $self->{lock_file}; | ||||
221 | } | ||||
222 | |||||
223 | ### No "dead" or stale locks found. | ||||
224 | } else { | ||||
225 | 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 | if ($try_lock_exclusive eq $has_lock_exclusive && @mine){ | ||||
235 | return $self; | ||||
236 | } | ||||
237 | } | ||||
238 | |||||
239 | ### If non-blocking, then kick out now. | ||||
240 | ### ($errstr might already be set to the reason.) | ||||
241 | if ($self->{lock_type} & LOCK_NB) { | ||||
242 | $errstr ||= "NONBLOCKING lock failed!"; | ||||
243 | return undef; | ||||
244 | } | ||||
245 | |||||
246 | ### wait a moment | ||||
247 | sleep(1); | ||||
248 | |||||
249 | ### but don't wait past the time out | ||||
250 | if( $quit_time && (time > $quit_time) ){ | ||||
251 | $errstr = "Timed out waiting for blocking lock"; | ||||
252 | return undef; | ||||
253 | } | ||||
254 | |||||
255 | # BLOCKING Lock, So Keep Trying | ||||
256 | } | ||||
257 | |||||
258 | ### clear up the NFS cache | ||||
259 | 1204 | 1.56ms | 1204 | 72.8ms | $self->uncache; # spent 72.8ms making 1204 calls to File::NFSLock::uncache, avg 60µs/call |
260 | |||||
261 | ### Yes, the lock has been acquired. | ||||
262 | 1204 | 685µs | delete $self->{unlocked}; | ||
263 | |||||
264 | 1204 | 7.96ms | return $self; | ||
265 | } | ||||
266 | |||||
267 | # spent 94.1ms (2.97+91.2) within File::NFSLock::DESTROY which was called 1204 times, avg 78µs/call:
# 1150 times (2.82ms+89.6ms) by Sympa::LockedFile::DESTROY at line 192 of /usr/local/libexec/sympa/Sympa/LockedFile.pm, avg 80µs/call
# 28 times (63µs+24µs) by Sympa::LockedFile::close at line 114 of /usr/local/libexec/sympa/Sympa/LockedFile.pm, avg 3µs/call
# 13 times (41µs+795µs) by File::NFSLock::do_lock_shared at line 379, avg 64µs/call
# 13 times (40µs+730µs) by File::NFSLock::do_unlock_shared at line 414, avg 59µs/call | ||||
268 | 1204 | 8.42ms | 1204 | 90.4ms | shift()->unlock(); # spent 91.2ms making 1204 calls to File::NFSLock::unlock, avg 76µs/call, recursion: max depth 1, sum of overlapping time 730µs |
269 | } | ||||
270 | |||||
271 | # spent 97.0ms (12.7+84.4) within File::NFSLock::unlock which was called 1232 times, avg 79µs/call:
# 1204 times (12.4ms+78.0ms) by File::NFSLock::DESTROY at line 268, avg 75µs/call
# 28 times (303µs+6.31ms) by Sympa::LockedFile::close at line 113 of /usr/local/libexec/sympa/Sympa/LockedFile.pm, avg 236µs/call | ||||
272 | 1232 | 274µs | my $self = shift; | ||
273 | 1232 | 698µs | if (!$self->{unlocked}) { | ||
274 | 1204 | 16.8ms | 1204 | 14.6ms | unlink( $self->{rand_file} ) if -e $self->{rand_file}; # spent 14.6ms making 1204 calls to File::NFSLock::CORE:ftis, avg 12µs/call |
275 | 1204 | 720µs | 13 | 5.34ms | if( $self->{lock_type} & LOCK_SH ){ # spent 5.34ms making 13 calls to File::NFSLock::do_unlock_shared, avg 411µs/call |
276 | $self->do_unlock_shared; | ||||
277 | }else{ | ||||
278 | 1191 | 1.49ms | 1191 | 65.2ms | $self->do_unlock; # spent 65.2ms making 1191 calls to File::NFSLock::do_unlock, avg 55µs/call |
279 | } | ||||
280 | 1204 | 735µs | $self->{unlocked} = 1; | ||
281 | 1204 | 880µs | foreach my $signal (@CATCH_SIGS) { | ||
282 | 2408 | 3.73ms | 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 | delete $SIG{$signal}; | ||||
290 | } | ||||
291 | } | ||||
292 | } | ||||
293 | 1232 | 2.00ms | 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 | my $file = shift; | ||||
304 | "$file.tmp.". time()%10000 .'.'. $$ .'.'. int(rand()*10000); | ||||
305 | } | ||||
306 | |||||
307 | # spent 83.4ms (15.2+68.2) within File::NFSLock::create_magic which was called 1204 times, avg 69µs/call:
# 1204 times (15.2ms+68.2ms) by File::NFSLock::new at line 144, avg 69µs/call | ||||
308 | 1204 | 299µs | $errstr = undef; | ||
309 | 1204 | 341µs | my $self = shift; | ||
310 | 1204 | 648µs | my $append_file = shift || $self->{rand_file}; | ||
311 | 1204 | 2.51ms | $self->{lock_line} ||= "$HOSTNAME $self->{lock_pid} ".time()." ".int(rand()*10000)."\n"; | ||
312 | 1204 | 51.9ms | 1204 | 47.6ms | open (my $fh,'>>', $append_file) or do { $errstr = "Couldn't open \"$append_file\" [$!]"; return undef; }; # spent 47.6ms making 1204 calls to File::NFSLock::CORE:open, avg 40µs/call |
313 | 1204 | 4.18ms | 1204 | 1.93ms | print $fh $self->{lock_line}; # spent 1.93ms making 1204 calls to File::NFSLock::CORE:print, avg 2µs/call |
314 | 1204 | 20.8ms | 1204 | 18.6ms | close $fh; # spent 18.6ms making 1204 calls to File::NFSLock::CORE:close, avg 15µs/call |
315 | 1204 | 3.47ms | return 1; | ||
316 | } | ||||
317 | |||||
318 | # spent 93.0ms (14.4+78.6) within File::NFSLock::do_lock which was called 1191 times, avg 78µs/call:
# 1191 times (14.4ms+78.6ms) by File::NFSLock::new at line 148, avg 78µs/call | ||||
319 | 1191 | 327µs | $errstr = undef; | ||
320 | 1191 | 392µs | my $self = shift; | ||
321 | 1191 | 555µs | my $lock_file = $self->{lock_file}; | ||
322 | 1191 | 360µs | my $rand_file = $self->{rand_file}; | ||
323 | 1191 | 300µs | my $chmod = 0600; | ||
324 | 1191 | 23.0ms | 1191 | 20.8ms | chmod( $chmod, $rand_file) # spent 20.8ms making 1191 calls to File::NFSLock::CORE:chmod, avg 17µs/call |
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 | 1191 | 36.9ms | 3573 | 30.9ms | my $success = link( $rand_file, $lock_file ) # spent 23.0ms making 1191 calls to File::NFSLock::CORE:link, avg 19µs/call
# spent 6.94ms making 1191 calls to File::NFSLock::CORE:ftis, avg 6µs/call
# spent 971µs making 1191 calls to File::NFSLock::CORE:stat, avg 816ns/call |
330 | && -e $rand_file && (stat _)[3] == 2; | ||||
331 | 1191 | 29.0ms | 1191 | 26.9ms | unlink $rand_file; # spent 26.9ms making 1191 calls to File::NFSLock::CORE:unlink, avg 23µs/call |
332 | |||||
333 | 1191 | 7.75ms | return $success; | ||
334 | } | ||||
335 | |||||
336 | # spent 4.51ms (266µs+4.24) within File::NFSLock::do_lock_shared which was called 13 times, avg 347µs/call:
# 13 times (266µs+4.24ms) by File::NFSLock::new at line 150, avg 347µs/call | ||||
337 | 13 | 6µs | $errstr = undef; | ||
338 | 13 | 6µs | my $self = shift; | ||
339 | 13 | 9µs | my $lock_file = $self->{lock_file}; | ||
340 | 13 | 5µs | my $rand_file = $self->{rand_file}; | ||
341 | |||||
342 | ### chmod local file to make sure we know before | ||||
343 | 13 | 4µs | my $chmod = 0600; | ||
344 | 13 | 6µs | $chmod |= $SHARE_BIT; | ||
345 | 13 | 308µs | 13 | 282µs | chmod( $chmod, $rand_file) # spent 282µs making 13 calls to File::NFSLock::CORE:chmod, avg 22µs/call |
346 | || die "I need ability to chmod files to adequatetly perform locking"; | ||||
347 | |||||
348 | ### lock the locking process | ||||
349 | 13 | 10µs | local $LOCK_EXTENSION = ".shared"; | ||
350 | 13 | 66µs | 13 | 0s | my $lock = new File::NFSLock { # spent 2.57ms making 13 calls to File::NFSLock::new, avg 198µs/call, recursion: max depth 1, sum of overlapping time 2.57ms |
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 | 13 | 273µs | 13 | 254µs | my $success = link( $rand_file, $lock_file); # spent 254µs making 13 calls to File::NFSLock::CORE:link, avg 20µs/call |
365 | 13 | 317µs | 13 | 299µs | unlink $rand_file; # spent 299µs making 13 calls to File::NFSLock::CORE:unlink, avg 23µs/call |
366 | 13 | 8µs | if ( !$success && | ||
367 | -e $lock_file && | ||||
368 | ((stat _)[2] & $SHARE_BIT) != $SHARE_BIT ){ | ||||
369 | |||||
370 | $errstr = 'Exclusive lock exists.'; | ||||
371 | return undef; | ||||
372 | |||||
373 | } elsif ( !$success ) { | ||||
374 | ### Shared lock exists, append my lock | ||||
375 | $self->create_magic ($self->{lock_file}); | ||||
376 | } | ||||
377 | |||||
378 | # Success | ||||
379 | 13 | 77µs | 13 | 836µs | return 1; # spent 836µs making 13 calls to File::NFSLock::DESTROY, avg 64µs/call |
380 | } | ||||
381 | |||||
382 | # spent 65.2ms (8.89+56.3) within File::NFSLock::do_unlock which was called 1191 times, avg 55µs/call:
# 1191 times (8.89ms+56.3ms) by File::NFSLock::unlock at line 278, avg 55µs/call | ||||
383 | 1191 | 66.0ms | 1191 | 56.3ms | return unlink shift->{lock_file}; # spent 56.3ms making 1191 calls to File::NFSLock::CORE:unlink, avg 47µs/call |
384 | } | ||||
385 | |||||
386 | # spent 5.34ms (325µs+5.01) within File::NFSLock::do_unlock_shared which was called 13 times, avg 411µs/call:
# 13 times (325µs+5.01ms) by File::NFSLock::unlock at line 275, avg 411µs/call | ||||
387 | 13 | 7µs | $errstr = undef; | ||
388 | 13 | 4µs | my $self = shift; | ||
389 | 13 | 10µs | my $lock_file = $self->{lock_file}; | ||
390 | 13 | 6µs | my $lock_line = $self->{lock_line}; | ||
391 | |||||
392 | ### lock the locking process | ||||
393 | 13 | 8µs | local $LOCK_EXTENSION = '.shared'; | ||
394 | 13 | 49µs | 13 | 3.48ms | my $lock = new File::NFSLock ($lock_file,LOCK_EX,62,60); # spent 3.48ms making 13 calls to File::NFSLock::new, avg 268µs/call |
395 | |||||
396 | ### get the handle on the lock file | ||||
397 | 13 | 3µs | my $fh; | ||
398 | 13 | 207µs | 13 | 170µs | if( ! open ($fh,'+<', $lock_file) ){ # spent 170µs making 13 calls to File::NFSLock::CORE:open, avg 13µs/call |
399 | if( ! -e $lock_file ){ | ||||
400 | return 1; | ||||
401 | }else{ | ||||
402 | die "Could not open for writing shared lock file $lock_file ($!)"; | ||||
403 | } | ||||
404 | } | ||||
405 | |||||
406 | ### read existing file | ||||
407 | 13 | 5µs | my $content = ''; | ||
408 | 13 | 138µs | 26 | 91µs | while(defined(my $line=<$fh>)){ # spent 91µs making 26 calls to File::NFSLock::CORE:readline, avg 3µs/call |
409 | 13 | 11µs | next if $line eq $lock_line; | ||
410 | $content .= $line; | ||||
411 | } | ||||
412 | |||||
413 | ### other shared locks exist | ||||
414 | 13 | 91µs | 13 | 770µs | if( length($content) ){ # spent 770µs making 13 calls to File::NFSLock::DESTROY, avg 59µs/call |
415 | seek $fh, 0, 0; | ||||
416 | print $fh $content; | ||||
417 | truncate $fh, length($content); | ||||
418 | close $fh; | ||||
419 | |||||
420 | ### only I exist | ||||
421 | }else{ | ||||
422 | 13 | 39µs | 13 | 23µs | close $fh; # spent 23µs making 13 calls to File::NFSLock::CORE:close, avg 2µs/call |
423 | 13 | 495µs | 13 | 475µs | unlink $lock_file; # spent 475µs making 13 calls to File::NFSLock::CORE:unlink, avg 37µs/call |
424 | } | ||||
425 | |||||
426 | } | ||||
427 | |||||
428 | # spent 72.8ms (13.7+59.1) within File::NFSLock::uncache which was called 1204 times, avg 60µs/call:
# 1204 times (13.7ms+59.1ms) by File::NFSLock::new at line 259, avg 60µs/call | ||||
429 | # allow as method call | ||||
430 | 1204 | 538µs | my $file = pop; | ||
431 | 1204 | 924µs | ref $file && ($file = $file->{file}); | ||
432 | 1204 | 1.46ms | 1204 | 4.61ms | my $rand_file = rand_file( $file ); # spent 4.61ms making 1204 calls to Sympa::LockedFile::__ANON__[/usr/local/libexec/sympa/Sympa/LockedFile.pm:49], avg 4µs/call |
433 | |||||
434 | ### hard link to the actual file which will bring it up to date | ||||
435 | 1204 | 70.8ms | 2394 | 54.5ms | return ( link( $file, $rand_file) && unlink($rand_file) ); # spent 29.6ms making 1190 calls to File::NFSLock::CORE:unlink, avg 25µs/call
# spent 24.8ms making 1204 calls to File::NFSLock::CORE:link, avg 21µs/call |
436 | } | ||||
437 | |||||
438 | sub newpid { | ||||
439 | my $self = shift; | ||||
440 | # Detect if this is the parent or the child | ||||
441 | 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 | my $patience = time + 10; | ||||
447 | while (time < $patience) { | ||||
448 | if (rename("$self->{lock_file}.fork",$self->{rand_file})) { | ||||
449 | # Child finished its newpid call. | ||||
450 | # Wipe the signal file. | ||||
451 | unlink $self->{rand_file}; | ||||
452 | last; | ||||
453 | } | ||||
454 | # Brief pause before checking again | ||||
455 | # to avoid intensive IO across NFS. | ||||
456 | 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 | 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 | $self->{lock_type} |= LOCK_SH; | ||||
470 | } | ||||
471 | } else { | ||||
472 | # This is the new child | ||||
473 | |||||
474 | # Fix lock_pid to the new pid. | ||||
475 | $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 | delete $self->{lock_line}; | ||||
482 | # Append a new lock_line to the lock_file. | ||||
483 | $self->create_magic($self->{lock_file}); | ||||
484 | |||||
485 | 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 | $self->{lock_type} |= LOCK_SH; | ||||
493 | } | ||||
494 | |||||
495 | # Create signal file to notify parent that | ||||
496 | # the lock_line entry has been delegated. | ||||
497 | open (my $fh, '>', "$self->{lock_file}.fork"); | ||||
498 | close($fh); | ||||
499 | } | ||||
500 | } | ||||
501 | |||||
502 | sub fork { | ||||
503 | my $self = shift; | ||||
504 | # Store fork response. | ||||
505 | my $pid = CORE::fork(); | ||||
506 | 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 | $self->newpid; | ||||
510 | } | ||||
511 | # Return original fork response | ||||
512 | 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 | ||||
sub File::NFSLock::CORE:chmod; # opcode | |||||
sub File::NFSLock::CORE:close; # opcode | |||||
# spent 33.2ms within File::NFSLock::CORE:ftis which was called 3599 times, avg 9µs/call:
# 1204 times (14.6ms+0s) by File::NFSLock::unlock at line 274, avg 12µs/call
# 1204 times (11.7ms+0s) by File::NFSLock::new at line 136, avg 10µs/call
# 1191 times (6.94ms+0s) by File::NFSLock::do_lock at line 329, avg 6µs/call | |||||
# spent 48.0ms within File::NFSLock::CORE:link which was called 2408 times, avg 20µs/call:
# 1204 times (24.8ms+0s) by File::NFSLock::uncache at line 435, avg 21µs/call
# 1191 times (23.0ms+0s) by File::NFSLock::do_lock at line 329, avg 19µs/call
# 13 times (254µs+0s) by File::NFSLock::do_lock_shared at line 364, avg 20µs/call | |||||
sub File::NFSLock::CORE:match; # opcode | |||||
sub File::NFSLock::CORE:open; # opcode | |||||
# spent 1.93ms within File::NFSLock::CORE:print which was called 1204 times, avg 2µs/call:
# 1204 times (1.93ms+0s) by File::NFSLock::create_magic at line 313, avg 2µs/call | |||||
# spent 91µs within File::NFSLock::CORE:readline which was called 26 times, avg 3µs/call:
# 26 times (91µs+0s) by File::NFSLock::do_unlock_shared at line 408, avg 3µs/call | |||||
# spent 971µs within File::NFSLock::CORE:stat which was called 1191 times, avg 816ns/call:
# 1191 times (971µs+0s) by File::NFSLock::do_lock at line 329, avg 816ns/call | |||||
# spent 114ms within File::NFSLock::CORE:unlink which was called 3598 times, avg 32µs/call:
# 1191 times (56.3ms+0s) by File::NFSLock::do_unlock at line 383, avg 47µs/call
# 1191 times (26.9ms+0s) by File::NFSLock::do_lock at line 331, avg 23µs/call
# 1190 times (29.6ms+0s) by File::NFSLock::uncache at line 435, avg 25µs/call
# 13 times (475µs+0s) by File::NFSLock::do_unlock_shared at line 423, avg 37µs/call
# 13 times (299µs+0s) by File::NFSLock::do_lock_shared at line 365, avg 23µs/call |