18.25.1 Problem
You want to have exclusive access to a file to
prevent it from being changed while you read or update it. If, for example, you
are saving guestbook information in a file, two users should be able to add
guestbook entries at the same time without clobbering each other's entries.
18.25.2 Solution
$fh = fopen('guestbook.txt','a') or die($php_errormsg);
flock($fh,LOCK_EX) or die($php_errormsg);
fwrite($fh,$_REQUEST['guestbook_entry']) or die($php_errormsg);
fflush($fh) or die($php_errormsg);
flock($fh,LOCK_UN) or die($php_errormsg);
fclose($fh) or die($php_errormsg);
18.25.3 Discussion
The file locking flock( ) provides is called
advisory file locking because flock( ) doesn't actually
prevent other processes from opening a locked file, it just provides a way for
processes to voluntarily cooperate on file access. All programs that need to
access files being locked with flock( ) need to set and release locks
to make the file locking effective.
There are two kinds of locks you can set with flock(
): exclusive locks and shared locks. An exclusive lock , specified
by LOCK_EX as the second argument to flock( ), can be held
only by one process at one time for a particular file. A shared lock, specified by LOCK_SH, can be held by more than
one process at one time for a particular file. Before writing to a file, you
should get an exclusive lock. Before reading from a file, you should get a
shared lock.
To unlock a file,
call flock( ) with LOCK_UN as the second argument. It's
important to flush any buffered data to be written to the file with fflush(
) before you unlock the file. Other processes
shouldn't be able to get a lock until that data is written.
By default, flock( ) blocks
until it can obtain a lock. To tell it not to block, add LOCK_NB to the
second argument:
$fh = fopen('guestbook.txt','a') or die($php_errormsg);
$tries = 3;
while ($tries > 0) {
$locked = flock($fh,LOCK_EX | LOCK_NB);
if (! $locked) {
sleep(5);
$tries--;
} else {
// don't go through the loop again
$tries = 0;
}
}
if ($locked) {
fwrite($fh,$_REQUEST['guestbook_entry']) or die($php_errormsg);
fflush($fh) or die($php_errormsg);
flock($fh,LOCK_UN) or die($php_errormsg);
fclose($fh) or die($php_errormsg);
} else {
print "Can't get lock.";
}
When the lock is nonblocking, flock( ) returns
right away even if it couldn't get a lock. The previous example tries three
times to get a lock on guestbook.txt, sleeping
five seconds between each try.
Locking with flock( ) doesn't work in all
circumstances, such as on some NFS implementations. Also, flock( )
isn't supported on Windows 95, 98, or ME. To simulate
file locking in these cases, use a directory as a exclusive lock
indicator. This is a separate empty directory whose presence indicates that the
data file is locked. Before opening a data file, create a lock directory and
then delete the lock directory when you're finished working with the data file.
Otherwise, the file access code is the same, as shown here:
$fh = fopen('guestbook.txt','a') or die($php_errormsg);
// loop until we can successfully make the lock directory
$locked = 0;
while (! $locked) {
if (@mkdir('guestbook.txt.lock',0777)) {
$locked = 1;
} else {
sleep(1);
}
}
if (-1 == fwrite($fh,$_REQUEST['guestbook_entry'])) {
rmdir('guestbook.txt.lock');
die($php_errormsg);
}
if (! fclose($fh)) {
rmdir('guestbook.txt.lock');
die($php_errormsg);
}
rmdir('guestbook.txt.lock') or die($php_errormsg);
A directory is used instead of a file to indicate a lock
because the mkdir( ) function fails to create a
directory if it already exists. This gives you a way, in one operation, to check
if the lock indicator exists and create it if it doesn't. Any error trapping
after the directory is created, however, needs to clean up by removing the
directory before exiting. If the directory is left in place, no future processes
can get a lock by creating the directory.
$locked = 0;
while (! $locked) {
if (! file_exists('guestbook.txt.lock')) {
touch('guestbook.txt.lock');
$locked = 1;
} else {
sleep(1);
}
}
This might fail under heavy load because you check for the
lock's existence with file_exists( ) and then
create the lock with touch( ). After one process
calls file_exists( ), another might call touch( ) before the
first calls touch( ). Both processes would then think they've got
exclusive access to the file when neither does. With mkdir( ) there's no gap between the checking for existence and
creation, so the process that makes the directory is ensured exclusive access.