Implementing Locking Using Plain Files
I would like to know if there is a way to move the file only if the target does not exist - in other words, move only if it does not cause an overwrite.
mv --update
seemed to be the first solution, however, if the timestamp of the source path is newer than the destination, the move will overwrite it and all attempts to get around this by changing the timestamp before the failure occurs.
I need this behavior to implement simple file-based locking, where the presence of a "lock" file indicates that the lock has been acquired.
I am using perl for this task, so if perl has this functionality it will be helpful. However, I need to make sure the move operation is atomic.
a source to share
But what will you do when someone else blocks? Quit and try later? Busy-wait?
If you don't need syncing, then a good bet sysopen
with the checkboxes checked O_EXCL
and O_CREAT
that will create a file only if it doesn't exist.
use Fcntl qw/ :DEFAULT /;
# ...
sysopen my $fh, $LOCKFILE, O_EXCL|O_CREAT
or die "$0: sysopen: $!";
But note the following caveat on the Linux man page open(2)
:
O_EXCL
only supported on NFS when using NFSv3 or higher kernel 2.6 or higher. In environments where NFS support isO_EXCL
not provided, programs that rely on it to perform locking tasks will contain a race condition. Portable programs that want to lock an atomic file using a lock file, and must avoid NFS support forO_EXCL
, can create a unique file on the same filesystem (for example, including the hostname and PID) and use itlink(2)
to reference the lock file. If itlink(2)
returns 0, the lock will succeed. Otherwise, usestat(2)
on a unique file to check if its link count has increased to 2, in which case the block will also succeed.
"Identity is more of a network filesystem than NFS," as the saying goes, so keep your coordination processes on the same machine if you can.
You can use flock
as in the below code:
#! /usr/bin/perl
use warnings;
use strict;
use Fcntl qw/ :DEFAULT :flock /;
my $LOCKFILE = "/tmp/mylock";
sub acquire_lock {
sysopen my $fh, $LOCKFILE, O_RDWR|O_CREAT or die "$0: open: $!";
flock $fh, LOCK_EX or die "$0: flock: $!";
$fh;
}
sub work {
for (1 .. 2) {
my $fh = acquire_lock;
print "$0: $$ has lock\n";
sleep rand 3;
close $fh or warn "$0: [$$] close: $!";
}
exit;
}
For demonstration, the code below spins up five children, which acquire the lock in turn:
my $KIDS = 5;
my %pids;
for (1 .. $KIDS) {
my $pid = fork;
die "$0: fork: $!" unless defined $pid;
$pid ? ++$pids{$pid} : work;
}
while (my $pid = wait) {
last if $pid == -1;
warn "$0: unknown child $pid" unless delete $pids{$pid};
}
warn "$0: still alive: " .
join(", " => sort { $a <=> $b } keys %pids) .
"\n"
if keys %pids;
Output example:
./kidlock: 26644 has lock ./kidlock: 26645 has lock ./kidlock: 26646 has lock ./kidlock: 26645 has lock ./kidlock: 26648 has lock ./kidlock: 26646 has lock ./kidlock: 26647 has lock ./kidlock: 26647 has lock ./kidlock: 26644 has lock ./kidlock: 26648 has lock
a source to share