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.

+2


a source to share


4 answers


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 is O_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 for O_EXCL

, can create a unique file on the same filesystem (for example, including the hostname and PID) and use it link(2)

to reference the lock file. If it link(2)

returns 0, the lock will succeed. Otherwise, use stat(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
+7


a source


mv -n

should do what you want.

From the man page:



 -n      Do not overwrite an existing file.  (The -n option overrides any previous -f or -i options.)

      

0


a source


    perldoc -f

rename

On * systems, NIX rename

is atomic and meets your requirements / requirements literally. However, generally for lockfiles, I often use the approach O_EXCL|O_CREAT

suggested in @ gbacon's answer .

0


a source


Note that if you are using Greg's solution it could be a race condition between the two instructions if another program tries to open it.

sysopen my $fh, $LOCKFILE, O_RDWR|O_CREAT or die "$0: open: $!";
flock $fh, LOCK_EX                        or die "$0: flock: $!";

      

0


a source







All Articles