start page | rating of books | rating of authors | reviews | copyrights

Book HomePHP CookbookSearch this book

18.25. Locking a File

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

Use flock( ) to provide advisory locking:

$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.

If you use a file as a lock indicator, the code to create it looks like:

$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.

18.25.4. See Also

Documentation on flock( ) at http://www.php.net/flock.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.