I'm aware of Perl's flock() function. The problem I'm having with that is it needs to open the file first before I can lock it (IIUC). Let me explain my problem.
I have a file than can be updated by many users simultaneously. A user can read the file, make a small change, then write it back to disk again.
Suppose user A needs to update the file. It opens it (using open()), then tries to flock() it. If user B has the file already open and locked, user A's flock() will block. As soon as User B writes the changed file to disk and unlocks it, user A's flock() will unblock. But at that moment, user A has a version of the file opened that is older than the version that has been written (to disk) by user B. So if User A then makes changes to the file and writes it to disk, User B's changes are lost. Or at least, that's how I think flock() works.
To prevent this from happening, I've written (in Perl) the LockFile() and UnlockFile() functions. What it does, when a file needs to be opened exclusively, it first creates a lock file for that specific file (if it doesn't exist) and locks that instead. If the lock succeeds, I can then open the true file I want exclusive use for. When I'm done with the true file, I write it to disk, then unlock the lock file.
In code:
BEGIN {
our $DIR_Locks;
my %filelocks;
my $lockdir = $DIR_Locks;
#############################
sub LockFile {
my ($FilePath, $blocking) = @_;
my $success;
$blocking = 1 if( !defined( $blocking ) );
my $LockFile = _getLockFileName( $FilePath );
my $fh = IO::File->new();
if( ! -f $LockFile ) {
open( $fh, ">$LockFile" );
close( $fh );
}
if( ! open( $fh, "$LockFile" ) ) {
return 0;
} else {
chmod( 0777, *$fh );
if( $blocking ) {
$success = flock( $fh, 2 );
} else {
$success = flock( $fh, 6 );
}
$filelocks{$LockFile} = $fh if( $success );
return $success;
}
}
#############################
sub UnlockFile {
my $FilePath = shift;
my $LockFile = _getLockFileName( $FilePath );
if( $filelocks{$LockFile} ) {
flock( $filelocks{$LockFile}, 8 );
close( $filelocks{$LockFile} );
}
}
#############################
sub _getLockFileName {
my $FilePath = shift;
my $LockFile;
my ($dir, $file) = ($FilePath =~ /(.*)\/(.*)/s);
if( $file eq "" && $dir eq "" ) {
$file = $FilePath;
}
$LockFile = "$lockdir/$file.lock";
return $LockFile;
}
}
But... It doesn't seem to work... My files still get corrupted every now and then. And in such a way that it looks like someone is opening the file at the same time somebody else is writing it to disk. I suspect it's because disk writes are probably cached meaning Perl think a write has finished, then unlocks the lock file and someone else opens the file while in reality the file is in the middle of being written to disk (by the cache handler).
My questions are:
Is my understanding of how flock() works correct? Or Have I actually created the problem by locking another file instead of the actual file I want exclusive use of?
Edit I should have added a sample of how I use this. Sorry. :)
LockFile( "somefile.dat" );
open( FILE, "somefile.dat" );
my $contents = SlurpFile( "somefile.dat" );
# SlurpFile() above reads the entire file at once
close( FILE );
# Do some stuff with $contents here
open( FILE, ">somefile.dat" );
print FILE $contents;
close( FILE );
UnlockFile( "somefile.dat" );
Now LockFile() doesn't actually lock "somefile.dat" but locks "somefile.dat.lock" (and creates it in the process if it doesn't exist). It just prevents other users (running the same script) from opening and changing the contents of "somefile.dat" while its contents are still being processed and haven't been written back to disk yet.
But what I sometimes see happening is that somefile.dat suddenly looses a lot of data, as if someone opened the file while it was still in the process of being written to disk. So a new user actually only read part of the file (from the beginning)...