Recipe 23.20. Locking a File


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

23.20.2. Solution

Use flock( ) to provide advisory locking, as shown in Example 23-49.

Using advisory file locking

<?php $fh = fopen('guestbook.txt','a')         or die($php_errormsg); flock($fh,LOCK_EX)                       or die($php_errormsg); fwrite($fh,$_POST['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); ?>

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

If any of your code uses flock( ) to lock a file, then all of your code should. For example, if one part of your program uses LOCK_EX to get an exclusive lock when writing to a file, then in any place where you must read the file, be sure to use LOCK_SH to get a shared lock on the file. If you don't do that, then a process trying to read a file can see the contents of the file while another process is writing to it.

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. Non-blocking locking is shown in Example 23-50.

Non-blocking locking

<?php $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,$_POST['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 non-blocking, 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 and older versions of Windows. To simulate file locking in these cases, use a directory as an exclusive lock indicator. This is a separate, empty directory whose presence indicates that the datafile is locked. Before opening a datafile, create a lock directory and then delete the lock directory when you're finished working with the datafile. Otherwise, the file access code is the same, as shown in Example 23-51.

Simulating locking with mkdir( )

<?php // loop until we can successfully make the lock directory $locked = 0; while (! $locked) {     if (@mkdir('guestbook.txt.lock',0777)) {         $locked = 1;     } else {         sleep(1);     } } $fh = fopen('guestbook.txt','a')         or die($php_errormsg); if (-1 == fwrite($fh,$_POST['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 instead of directory as a lock indicator, the code to create it looks something like Example 23-52.

Error-prone file locking

$locked = 0; while (! $locked) {     if (! file_exists('guestbook.txt.lock')) {         touch('guestbook.txt.lock');         $locked = 1;     } else {         sleep(1);     } }

Example 23-52 fails under heavy load because it checks for the lock's existence with file_exists( ) and then creates 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 really 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.

23.20.4. See Also

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




PHP Cookbook, 2nd Edition
PHP Cookbook: Solutions and Examples for PHP Programmers
ISBN: 0596101015
EAN: 2147483647
Year: 2006
Pages: 445

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net