PHP Files Locking a File - Web Development and Design | Tutorial for Java, PHP, HTML, Javascript PHP Files Locking a File - Web Development and Design | Tutorial for Java, PHP, HTML, Javascript

Breaking

Post Top Ad

Post Top Ad

Thursday, July 11, 2019

PHP Files Locking a File

PHP Files 



Locking a File

Problem

You want to have exclusive access to a file to prevent it from being changed while you read or update it. For example, if 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.

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,$_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);

Discussion

In general, if you find yourself needing to lock a file, it’s best to see if there’s an alternative way to solve your problem. Often you can (and should!) use a database (or SQLite, if you don’t have access to a standalone database) instead.

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.

You can set two kinds of locks 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, 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, as shown:

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

       // 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 a directory as a lock indicator, the code to create it looks something like this:

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

This code 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 checking for existence and creation, so the process that makes the directory is ensured exclusive access.

No comments:

Post a Comment

Post Top Ad