LockFile::Simple - simple file locking scheme
use LockFile::Simple qw(lock trylock unlock); # Simple locking using default settings lock("/some/file") || die "can't lock /some/file\n"; warn "already locked\n" unless trylock("/some/file"); unlock("/some/file"); # Build customized locking manager object $lockmgr = LockFile::Simple->make(-format => '%f.lck', -max => 20, -delay => 1, -nfs => 1); $lockmgr->lock("/some/file") || die "can't lock /some/file\n"; $lockmgr->trylock("/some/file"); $lockmgr->unlock("/some/file"); $lockmgr->configure(-nfs => 0); # Using lock handles my $lock = $lockmgr->lock("/some/file"); $lock->release;
This simple locking scheme is not based on any file locking system calls
such as flock()
or lockf()
but rather relies on basic file system
primitives and properties, such as the atomicity of the write()
system
call. It is not meant to be exempt from all race conditions, especially over
NFS. The algorithm used is described below in the ALGORITHM section.
It is possible to customize the locking operations to attempt locking once every 5 seconds for 30 times, or delete stale locks (files that are deemed too ancient) before attempting the locking.
The locking alogrithm attempts to create a lockfile using a temporarily redefined umask (leaving only read rights to prevent further create operations). It then writes the process ID (PID) of the process and closes the file. That file is then re-opened and read. If we are able to read the same PID we wrote, and only that, we assume the locking is successful.
When locking over NFS, i.e. when the one of the potentially locking processes could access the lockfile via NFS, then writing the PID is not enough. We also write the hostname where locking is attempted to ensure the data are unique.
Customization is only possible by using the object-oriented interface,
since the configuration parameters are stored within the object. The
object creation routine make
can be given configuration parmeters in
the form a "hash table list", i.e. a list of key/value pairs. Those
parameters can later be changed via configure
by specifying a similar
list of key/value pairs.
To benefit from the bareword quoting Perl offers, all the parameters must
be prefixed with the -
(minus) sign, as in -format
for the format
parameter.. However, when querying the object, the minus must be omitted,
as in $obj->format
.
Here are the available configuration parmeters along with their meaning, listed in alphabetical order:
A function pointer to dereference when an error is to be reported. By default, it redirects to the logerr() routine if you have Log::Agent installed, to Perl's warn() function otherwise.
You may set it explicitely to \&LockFile::Simple::core_warn
to force the
use of Perl's warn() function, or to undef
to suppress logging.
.lock
(note that .
is part
of the extension and can therefore be changed). Ignored when format is
also used.
Using this parmeter supersedes the ext parmeter. The formatting string specified is run through a rudimentary macro expansion to derive the lockfile path from the file to be locked. The following macros are available:
%% A real % sign %f The full file path name %D The directory where the file resides %F The base name of the file %p The process ID (PID)
The default is to use the locking extension, which itself is .lock
, so
it is as if the format used was %f.lock
, but one could imagine things
like /var/run/%F.%p
, i.e. the lockfile does not necessarily lie besides
the locked file (which could even be missing).
When locking, the locking format can be specified to supersede the object configuration itself.
A function pointer to dereference when a warning is to be issued. By default, it redirects to the logwarn() routine if you have Log::Agent installed, to Perl's warn() function otherwise.
You may set it explicitely to \&LockFile::Simple::core_warn
to force the
use of Perl's warn() function, or to undef
to suppress logging.
Each of those configuration attributes can be queried on the object directly:
$obj = LockFile::Simple->make(-nfs => 1); $on_nfs = $obj->nfs;
Those are pure query routines, i.e. you cannot say:
$obj->nfs(0); # WRONG $obj->configure(-nfs => 0); # Right
to turn of the NFS attribute. That is because my OO background chokes at having querying functions with side effects.
The OO interface documented below specifies the signature and the
semantics of the operations. Only the lock
, trylock
and
unlock
operation can be imported and used via a non-OO interface,
with the exact same signature nonetheless.
The interface contains all the attribute querying routines, one for each configuration parmeter documented in the CUSTOMIZING section above, plus, in alphabetical order:
Attempt to lock the file, using the optional locking format if specified, otherwise using the default format scheme configured in the object, or by simply appending the ext extension to the file.
If the file is already locked, sleep delay seconds before retrying, repeating try/sleep at most max times. If warning is configured, a first warning is emitted after waiting for wmin seconds, and then once every wafter seconds, via the wfunc routine.
Before the first attempt, and if hold is non-zero, any existing lockfile is checked for being too old, and it is removed if found to be stale. A warning is emitted via the wfunc routine in that case, if allowed.
Likewise, if stale is non-zero, a check is made to see whether any locking process is still around (only if the lock holder is on the same machine when NFS locking is configured). Should the locking process be dead, the lockfile is declared stale and removed.
Returns a lock handle if the file has been successfully locked, which does not necessarily needs to be kept around. For instance:
$obj->lock('ppp', '/var/run/ppp.%p'); <do some work> $obj->unlock('ppp');
or, using OO programming:
my $lock = $obj->lock('ppp', '/var/run/ppp.%p') ||; die "Can't lock for ppp\n"; <do some work> $lock->relase; # The only method defined for a lock handle
i.e. you don't even have to know which file was locked to release it, since there is a lock handle right there that knows enough about the lock parameters.
Same as lock except that it immediately returns false and does not sleep if the to-be-locked file is busy, i.e. already locked. Any stale locking file is removed, as lock would do anyway.
Returns a lock hande if the file has been successfully locked.
The algorithm is not bullet proof. It's only reasonably safe. Don't bet the integrity of a mission-critical database on it though.
The sysopen() call should probably be used with the O_EXCL|O_CREAT
flags
to be on the safer side. Still, over NFS, this is not an atomic operation
anyway.
BEWARE: there is a race condition between the time we decide a lock is
stale or too old and the time we unlink it. Don't use -stale
and set
-hold
to 0 if you can't bear with that idea, but recall that this race
only happens when something is already wrong. That does not make it right,
nonetheless. ;-)
Raphael Manfredi <Raphael_Manfredi@pobox.com>
File::Flock(3).