The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

File::SharedNFSLock - Inter-machine locking on NFS volumes

SYNOPSIS

  use File::SharedNFSLock;
  my $flock = File::SharedNFSLock->new(
    file => 'some_file_on_nfs',
  );
  my $got_lock = $flock->lock(); # blocks for $timeout_acquire seconds if necessary
  if ($got_lock) {
    # hack hack hack...
  }
  $flock->unlock;
  
  # meanwhile, on another machine or in another process:
  my $flock = File::SharedNFSLock->new(
    file => 'some_file_on_nfs',
  );
  my $got_lock = $flock->lock(); # blocks for timeout or until first process is done
  # ...

DESCRIPTION

NFS (at least before v4) is evil. File locking on NFS volumes is worse. This module attempts to implement file locking on NFS volumes using lock files and hard links. It's in production use at our site, but if it doesn't work for you, I'm not surprised!

Note that the lock files are always written to the same directory as the original file! There is always one lock file per process that tries to acquire the lock. This module does NOT do signal handling. You will have to do that yourself.

ALGORITHM

I use the fact that hard links are (err, appear to be) atomic even with NFS. So I write a process-specific, unique lock file and then hard-link it to the real thing. Afterwards, stat() tells me the number of hard-linked instances of the file (when polling my unique, private file). This indicates that I have acquired the lock.

The algorithm was snatched from a document called NFS Considered Harmful by Shane Kerr. I found it at http://www.time-travellers.org/shane/papers/NFS_considered_harmful.html. Look for chapter III, List of Concerns, concern d: Exclusive File Creation. The described workaround is, I quote the above:

  The solution for performing atomic file locking using a lockfile
  is to create a unique file on the same fs (e.g., incorporating
  hostname and pid), use link(2) to make a link to the lockfile and
  use stat(2) on the unique file to check if its link count has
  increased to 2. Do not use the return value of the link() call.

METHODS

new

Creates a new lock object but does NOT attempt to acquire the lock (see lock() below). Takes named arguments. All times in the parameters are in seconds and can be floating point values, indicating a fraction of a second.

Mandatory argument: file pointing at the file that is to be locked.

Optional arguments: poll_interval indicates the number of seconds to wait between attempts to acquire the lock. Defaults to 1 second.

timeout_acquire indicates the total time that may be spent trying to acquire a lock when lock() is called. After this time has elapsed, we bail out without having acquired a lock. Default: 60 seconds. If set to 0, the lock acquisition effectively becomes non-blocking.

timeout_stale indicates the number of seconds since the creation of an existing lock file, after which this alien lock file is to be considered stale. A stale lock will be removed and replaced with our own lock (watch out!). Default: 5 minutes. Set this to 0 to disable the feature.

lock

Attempts to acquire a lock on the file. Returns 1 on success, 0 on failure (time out).

unlock

Releases the lock, deletes the lock file. This is automatically called on destruction of the lock object!

locked

Checks whether we have the lock on the file.

Note: Fairly expensive operation requiring a stat call.

CAVEATS

This isn't as well tested as it should be even though it is being used in production here. Do your own testing.

There are no unit tests! (Patches welcome!)

Born out of frustration with existing locking modules.

Probably contains hidden race conditions.

SEE ALSO

File::NFSLock, but that doesn't work for multiple machines (just for a single machine and multiple processes).

Time::HiRes is used to implement fractional-second sleep() and time() calls.

AUTHOR

Steffen Mueller, <smueller@cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2010 by Steffen Mueller

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available.