# ================================================================== # Gossamer Threads Module Library - http://gossamer-threads.com/ # # GT::Lock # Author: Scott Beck # CVS Info : 087,071,086,086,085 # $Id: Lock.pm,v 1.7 2007/02/11 21:56:56 sbeck Exp $ # # Copyright (c) 2005 Gossamer Threads Inc. All Rights Reserved. # ================================================================== # # Description: a small autonomous locking module. # package GT::Lock; use vars qw/@EXPORT_OK $error $SAFETY $ERRORS/; use strict; use bases 'Exporter' => '', 'GT::Base' => ''; use constants MASK => 0777, SLEEPTIME => 0.05, TIMEOUT => 10, LOCK_TRY => 1, LOCK_FORCE => 2; use POSIX qw/errno_h/; use GT::TempFile; $ERRORS = { 'TIMEOUT' => 'Could not lock %s; We timed out', 'NOLOCK' => 'No lock was found for name %s' }; @EXPORT_OK = qw/lock unlock LOCK_FORCE LOCK_TRY/; sub lock { #--------------------------------------------------------------------------------- defined( $_[0] ) or GT::Lock->fatal( BADARGS => 'First argument must be a defined value' ); my $name = escape($_[0]); my $timeout = defined $_[1] ? $_[1] : TIMEOUT; my $opt = defined $_[2] ? $_[2] : LOCK_FORCE; my $max_age = $_[3]; my $tmp_dir = $GT::TempFile::TMP_DIR || GT::TempFile::find_tmpdir(); my $lock_dir = "$tmp_dir/$name"; if ($max_age and -d $lock_dir and time - (stat $lock_dir)[9] > $max_age) { rmdir $lock_dir or $! == ENOENT or GT::Lock->fatal(RMDIR => $lock_dir, "$!"); } my $start_time = time; until (mkdir $lock_dir, MASK) { select undef, undef, undef, SLEEPTIME; if ($timeout and $start_time + $timeout < time) { if ($opt == LOCK_TRY) { return GT::Lock->warn(TIMEOUT => unescape($name)); } else { # XXX - 2 appears to be No such file or directory, but may not be entirely portable. unless (rmdir $lock_dir and $! != ENOENT) { # The rmdir failed which *may* be due to two processes # holding the same lock then the other one deleting it # just before this one attempted to. In such a case, we # allow double the timeout to try to avoid the race - # though this reduces the frequency of race conditions, it # does not completely eliminate it. if ($timeout and $start_time + 2 * $timeout < time) { GT::Lock->fatal(RMDIR => $lock_dir, "$!"); } } } } } return 1; } sub unlock { #-------------------------------------------------------------------------------- my $name = escape($_[0]); my $tmp_dir = $GT::TempFile::TMP_DIR || GT::TempFile::find_tmpdir(); my $lock_dir = "$tmp_dir/$name"; if (-d $lock_dir) { rmdir $lock_dir or return GT::Lock->fatal(RMDIR => $lock_dir, "$!"); } else { return GT::Lock->warn(NOLOCK => $name); } return 1; } sub escape { #-------------------------------------------------------------------------------- my $toencode = $_[0]; return unless (defined $toencode); $toencode =~ s/([^\w. -])/sprintf "%%%02X", ord $1/eg; $toencode =~ s/ /%20/g; return $toencode; } sub unescape { #-------------------------------------------------------------------------------- my $todecode = $_[0]; return unless (defined $todecode); $todecode =~ tr/+/ /; # pluses become spaces $todecode =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge; return $todecode; } 1; __END__ =head1 NAME GT::Lock - a small autonomous locking module. =head2 SYNOPSIS use GT::Lock qw/lock unlock LOCK_TRY LOCK_FORCE/; # attempt to lock foobar for 10 seconds if (lock 'foobar', 10, LOCK_TRY) { # do some code that needs to be locked unlock 'foobar'; } else { # oops out lock failed die "Lock failed: $GT::Lock::error\n"; } =head1 DESCRIPTION GT::Lock is a very simple module to impliment autonomous named locking. Locking can be used for many things but is most commonly used to lock files for IO to them. Nothing is exported by default. You may request the lock, unlock routines be exported. You can also get the two constants for lock types exported: C and C. =head2 lock - Lock a name. lock NAME [, TIMOUT, TYPE, AGE ] This method is used to create a lock. Its arguments are the name you wish to give the lock, the timeout in seconds for the lock to happen, the type of lock, and the maximum lock age (in seconds). The types are C and C. If C is given a lock always succeeds, e.g. if the lock times out the lock is removed and your lock succeeds. Try attempts to get the lock and returns false if the lock can not be had in the specified C. If C is zero this method will attempt to lock forever. C defaults to 10 seconds. The AGE parameter can be used to ensure that stale locks are not preserved - if the lock already exists and is older than AGE seconds, it will be removed before attempting to get the lock. Omitting it uses the default value, undef, which does not attempt to remove stale locks. =head2 unlock - unlock a name. unlock NAME This method is used to unlock a name. It's argument is the name of the lock to unlock. Returns true on success and false on errors and sets the error in $GT::Lock::error. =head1 DEPENDANCIES L depends on L, L, and L. =head1 COPYRIGHT Copyright (c) 2005 Gossamer Threads Inc. All Rights Reserved. http://www.gossamer-threads.com/ =head1 VERSION Revision: $Id: Lock.pm,v 1.7 2007/02/11 21:56:56 sbeck Exp $ =cut