I have some code that manages a lockfile mechanism used to keep various processes from working on the same things at once. The basic idea is that before a process starts a task it tries to get a lock on the task id. If it cant get the lock it tries to work on something else. What i wanted to do was to have it work like this:
But what happens is that unless I add a step 5.5 which is "Unlock the file" before we "Lock the file in shared mode" the lock status doesnt change from LOCK_EX to LOCK_SH. Thus other process sit waiting for ever. If I do put in the LOCK_UN step then it seems to work, but I cant help but feel there is a race condition involved if I do.
use Fcntl ':flock';
use POSIX;
sub iso_time { POSIX::strftime ("%Y-%m-%d %H:%M:%S",localtime($_[0]||t
+ime)) }
sub OnDestroy::DESTROY { shift(@_)->() }
sub OnDestroyDo(&) { bless shift(@_),"OnDestroy" }
sub get_lock {
my ($lock_dir,$name)=@_;
my $debug=1;
$name=~s/[^-\w.#!\@~=+%\$]//g;
my $lockfile=catfile($lock_dir,$name.'.lock');
print "Trying lockfile $lockfile\n";
sysopen(my $FH, $lockfile, O_RDWR | O_CREAT)
or do { warn "can't open $lockfile: $!" if $debug;
return;
};
# autoflush $FH
select( (select($FH), $|++)[0] );
my ( $time, $process, $lname );
if (flock( $FH, LOCK_EX | LOCK_NB )) {
( $time, $process, $lname )=split /\|/,join "",<$FH>;
seek $FH, 0, 0 or die "Failed rewind:$!";
if ($debug) {
if ($process) {
print "\tLockfile appears to be abandonded by Process
+#$process started at $time\n"
} else {
print "\tLockfile appears to be unprocessed\n"
}
}
my $lock_msg=join("|", iso_time(), $$, $name)."\n";
print "Locking $lockfile : $lock_msg";
print $FH $lock_msg;
truncate($FH, tell($FH)) or die "Failed to truncate:$!";
# if I remove this it doesnt work on my Win2k box
flock($FH, LOCK_UN) or die "sharedlock: $!";
# but if I leave it in then it seems like a race condition is
+possible.
flock($FH, LOCK_SH|LOCK_NB) or die "sharedlock: $!";
return OnDestroyDo {
print "\tFinished with and removing $lockf
+ile\n";
close $FH or die "Failed to close \$FH:$!"
+;
unlink $lockfile or die "Failed to unlink
+$lockfile\n";
undef $FH;
};
} elsif (flock($FH, LOCK_SH|LOCK_NB)) {
( $time, $process, $lname )=split /\|/,join "",<$FH>;
print "\tLockfile $lockfile appears to be locked by Process #$
+process at $time\n"
if $debug;
} else {
print "Failed to get lock on $lockfile, not sure why.\n";
}
return
}