search for in the  
<filetypefnmatch>
Last updated: Thu, 19 May 2005

flock

(PHP 3 >= 3.0.7, PHP 4, PHP 5)

flock -- Portable advisory file locking

Description

bool flock ( resource handle, int operation [, int &wouldblock] )

PHP supports a portable way of locking complete files in an advisory way (which means all accessing programs have to use the same way of locking or it will not work).

Note: flock() is mandatory under Windows.

flock() operates on handle which must be an open file pointer. operation is one of the following values:

  • To acquire a shared lock (reader), set operation to LOCK_SH (set to 1 prior to PHP 4.0.1).

  • To acquire an exclusive lock (writer), set operation to LOCK_EX (set to 2 prior to PHP 4.0.1).

  • To release a lock (shared or exclusive), set operation to LOCK_UN (set to 3 prior to PHP 4.0.1).

  • If you don't want flock() to block while locking, add LOCK_NB (4 prior to PHP 4.0.1) to operation.

flock() allows you to perform a simple reader/writer model which can be used on virtually every platform (including most Unix derivatives and even Windows). The optional third argument is set to TRUE if the lock would block (EWOULDBLOCK errno condition). The lock is released also by fclose() (which is also called automatically when script finished).

Returns TRUE on success or FALSE on failure.

Example 1. flock() example

<?php

$fp
= fopen("/tmp/lock.txt", "w+");

if (
flock($fp, LOCK_EX)) { // do an exclusive lock
  
fwrite($fp, "Write something here\n");
  
flock($fp, LOCK_UN); // release the lock
} else {
   echo
"Couldn't lock the file !";
}

fclose($fp);

?>

Note: Because flock() requires a file pointer, you may have to use a special lock file to protect access to a file that you intend to truncate by opening it in write mode (with a "w" or "w+" argument to fopen()).

Warning

flock() will not work on NFS and many other networked file systems. Check your operating system documentation for more details.

On some operating systems flock() is implemented at the process level. When using a multithreaded server API like ISAPI you may not be able to rely on flock() to protect files against other PHP scripts running in parallel threads of the same server instance!

flock() is not supported on antiquated filesystems like FAT and its derivates and will therefore always return FALSE under this environments (this is especially true for Windows 98 users).



User Contributed Notes
flock
eddi.to-grip.de
17-Apr-2005 10:53
When using a multithreaded server like apache with worker.c on Linux you can use follow lines:

<?php
function fprotect($key,
          
$file,
          
$content=FALSE,
          
$fopen_mode='w',
          
$chmod=0600,
          
$sem_mode=0600
          
)
   {
  
$semaphor=sem_get($key,1,$sem_mode);
  
sem_acquire($semaphor);

   if(
$content)
       {
       if(
$dat=fopen($file,$fopen_mode))
           {
          
chmod($file,$chmod);
          
fputs($dat,$content);
          
fclose($dat);
           }
       else    return(
FALSE);

       }
   else    return(
file_get_contents($datei));

  
sem_release($semaphor);

   return(
TRUE);
   }
?>

Also this should work instead of flock() using a non-multithreaded server on Linux.

Greetings form Berlin :)
zmey at kmv dot ru
28-Mar-2005 12:33
2pentek_imre at mailbox dot hu:
flock() alone is not enough to handle race conditions, you will require the fseek() function too:
<?php
$f
=fopen($filename,"a+") or die();
flock($f,LOCK_EX) or die();
// in case some other script has appended to the file while we were waiting on flock()...
fseek($f,0,SEEK_END);
//here write some lines to the file -- not included
//then close:
// flock($f,LOCK_UN) or die(); // fclose will remove all locks automatically
fclose($f) or die();
?>

But the problem with interrupted script under apache still cannot be addressed gracefully. If the script is interrupted while writing to the file, the file's contents will get corrupt. Temporary files are nice, but they do not guarantee that your information will not be lost. For example:

<?php
// open file for read-write access
$f=fopen($filename,"r+");
flock($f,LOCK_EX) or die();
$ft=fopen($filename.(getmypid()),"w") or die();
//here we copy data from the locked file to temporary one, modifying it in the process
//then close temporary file:
fclose($ft) or die();
// Now we have two files: temporary file and locked source file.
// This trick works under *nix, but has some drawbacks.
// For Windows, we must first unlink the original file.
rename($filename.(getmypid()),$filename) or die();
// this will unlock the hidden, ex-original file under *nix, but will fail under win.
fclose($f) or die();
?>

Under *nix, this code works just fine, but still can lead to data loss. Look:
1. We lock the original file.
2. Another copy of the script tries to lock the file (say, to modify some other part of file). It waits till we release the lock.
3. We make temporary file with modified data, and close it.
4. Rename succeeds, but in fact it works this way:
4.1 Original file's inode is marked as deleted, but the file is still on the disk because there are open handles to it. The file will be actually deleted only after the last handle closes.
4.2 Temporary file's inode is corrected so that its name is the name of the original file.

What are the consequences? The second script, that was patiently waiting for us to unlock the file, gets access to the file, BUT: the file is not the new, updated version! The script's handle still points to the original file, although it is not accessible by any other program. So the script happily processes the OLD version of the original file, and replaces the files once again. Voila - the updates made by the first script are all lost!

Under Windows, we will not be able to rename the temporary file without unlinking the original file first. Besides, we will not be able to unlink the file without unlocking it (Windows uses mandatory locking)! So we will not be able to reliably replace the original file with the temporary one without some trickery (e.g., use of external lock files).
maciej.trebacz[at]gmail.com
20-Dec-2004 12:50
I have an idea about race condintions. How about make a seperate check for integrity of file somewhere in the middle of the script ?

Let's take a look on this concept:

1. Some init code
2. Read whole using file() or other function
3. Do the rest that script want to do
4. After writing something to file, check for the file integrity.  It can be done in several ways, depending on what you're putting to file. I'm doing file database's so it's rather easy. If the file fails this check, open it, lock it and use data read in (2) to rewrite it. Also a good idea will be putting this whole procedure into while...do, so you ensure that the file will be good.
5. Rest of the script

It may also not be perfect, but I think it's a very big chance to prevent scripts to mess your files.
mic at mymail dot bz
02-Dec-2004 06:54
2 rehfeld.us

in your code
<?php
...
      
$timeStart = microtime_float();
       do {
           if ((
microtime_float() - $timeStart) > $timeLimit) break;
          
$locked = @mkdir($lockDir);
       } while (
$locked === false);
...
?>

$timeStart and return value of microtime_float() is _float_, i.e. for example $timestart = 1101991367.393253
and microtime_float() - $timeStart will be like 0.00123
but $timeLimit is _integer_ and much bigger

So code will work but execution timeout ocured.

you must set value of $timeLimit in float, i.e. $timeLimit = 0.03
pentek_imre at mailbox dot hu
31-Oct-2004 05:51
flock isn't good in race conditions. I accept that it can correctly lock file and can correctly block php processes if file is locked, but anyway this function isn't the right way to manage a race condition.
Let's have a look at this code:
<?php
$f
=fopen($filename,"a+") or die();
flock($f,LOCK_EX) or die();
//here write some lines to the file -- not included
//then close:
flock($f,LOCK_UN) or die();
fclose($f) or die();
?>
Then generate a race situation with two php processes:
1: open file ok, no file found, create
2: open file ok, file found seek to the end (0lenght file so to the beginning)
1: lock file ok
2: flock waits since file is already locked.
1: write ok
1: unlock ok
2: flock ok this process now continues
1: fclose ok
2: write something, but due to prebuffering the file is now empty, so content written by 1 is now unconsidered, forgotten.
2: unlock ok
2: fclose ok
file will have only the content from process 2
this is the situation if you use r+ too, no matter if you used fflush or not.
conclusion: you may want to create a separate lock file!
separate lock file will behave like in the previous example too, so let's try LOCK_NB and $wouldblock, file close and reopen.
let's see this example:
<?php
$file
="/tmp/phplockfile";
do
 {
  if(isset(
$f))
   {
  
flock($f,LOCK_UN);
  
fclose($f);
   }
 
$f=fopen($file,"a") or die();
 
flock($f,LOCK_EX+LOCK_NB,$W);
//  sleep(1);
 
}
while (
$W==1);

//lock is mine:
echo $_SERVER["UNIQUE_ID"]." ".date("r")."\n";
sleep(1);
echo
$_SERVER["UNIQUE_ID"]." ".date("r")."\n";

//release the lock:
flock($f,LOCK_UN);
fclose($f);
?>
I tried this code for 10 (ten) parellel processes. only three of them succeeds to lock the file and unlock it, the other seven quits with execuition timeout. uncommenting the sleep(1); won't help too, just execution will be longer (30 sec is counted not as real time but as cpu time)
I tried random usleep too, as I remember this wasn't helped too.
Remember that file close and reopen is a must becouse processes may write to the file, and this way these extra bytes will be considered too.
rehfeld.us
28-Sep-2004 06:04
<?php

/*
 * I hope this is usefull.
 * If mkdir() is atomic,
 * then we do not need to worry about race conditions while trying to make the lockDir,
 * unless of course were writing to NFS, for which this function will be useless.
 * so thats why i pulled out the usleep(rand()) peice from the last version
 *
 * Again, its important to tailor some of the parameters to ones indivdual usage
 * I set the default $timeLimit to 3/10th's of a second (maximum time allowed to achieve a lock),
 * but if your writing some extrememly large files, and/or your server is very slow, you may need to increase it.
 * Obviously, the $staleAge of the lock directory will be important to consider as well if the writing operations might take  a while.
 * My defaults are extrememly general and you're encouraged to set your own
 *
 * $timeLimit is in microseconds
 * $staleAge is in seconds
 *
 *
 */

function microtime_float()
{
   list(
$usec, $sec) = explode(' ', microtime());
   return ((float)
$usec + (float)$sec);
}

function
locked_filewrite($filename, $data, $timeLimit = 300000, $staleAge = 5)
{
  
ignore_user_abort(1);
  
$lockDir = $filename . '.lock';

   if (
is_dir($lockDir)) {
       if ((
time() - filemtime($lockDir)) > $staleAge) {
          
rmdir($lockDir);
       }
   }

  
$locked = @mkdir($lockDir);

   if (
$locked === false) {
      
$timeStart = microtime_float();
       do {
           if ((
microtime_float() - $timeStart) > $timeLimit) break;
          
$locked = @mkdir($lockDir);
       } while (
$locked === false);
   }

  
$success = false;

   if (
$locked === true) {
      
$fp = @fopen($filename, 'a');
       if (@
fwrite($fp, $data)) $success = true;
       @
fclose($fp);
      
rmdir($lockDir);
   }

  
ignore_user_abort(0);
   return
$success;
}

?>
chuck
22-Sep-2004 03:15
Creating lock files and testing for their existence is absolutely wrong, NFS or not.  The only file operations that are guaranteed to be atomic are mkdir() and symlink().  And those aren't atomic over NFS (or other network filesystems) either.

Short answer: create lock directories, not files.  Don't stat the directory to test, simply try to create it and see if it fails.  And all locking bets are always off with NFS.
rehfeld.us
12-Sep-2004 09:48
This is based on the function by jeroenl at see dot below
and the suggestion of jeppe at bundsgaard dot net

please note this wont work on windoes unless using php5, due to the use of usleep()

one may wish to tailor some of the values to better suit thier application. the rand() values, the number of loop attempts, and the max age of the lockfile are what im referring to

<?php

function locked_filewrite($filename, $data) {
  
ignore_user_abort(1);
  
$lockfile = $filename . '.lock';

  
// if a lockfile already exists, but it is more than 5 seconds old,
   // we assume the last process failed to delete it
   // this would be very rare, but possible and must be accounted for
  
if (file_exists($lockfile)) {
       if (
time() - filemtime($lockfile) > 5) unlink($lockfile);
   }

  
$lock_ex = @fopen($lockfile, 'x');
   for (
$i=0; ($lock_ex === false) && ($i < 20); $i++) {
      
clearstatcache();
      
usleep(rand(9, 999));
      
$lock_ex = @fopen($lockfile, 'x');
   }

  
$success = false;
   if (
$lock_ex !== false) {
      
$fp = @fopen($filename, 'a');
       if (@
fwrite($fp, $data)) $success = true;
       @
fclose($fp);
      
fclose($lock_ex);
      
unlink($lockfile);
   }

  
ignore_user_abort(0);
   return
$success;
}

?>
rudy dot metzger at pareto dot nl
08-Sep-2004 05:31
Like a user already noted, most Linux kernels (at least the Redhat ones) will return false, even if you locked the file. This is because the lock is only ADVISORY (you can check that in /proc/locks). What you have to do there is to evalute the 3rd parameter of flock(), $eWouldBlock. See for an example below. Note however that if you
lock the file in non blocking mode, flock() will work as expected (and blocks the script).

<?php
                                                                              
$fp
= fopen( "/var/lock/process.pid", "a" );
if ( !
$fp || !flock($fp,LOCK_EX|LOCK_NB,$eWouldBlock) || $eWouldBlock ) {
 
fputs( STDERR, "Failed to acquire lock!\n" );
  exit;
}
                                                                              
// do your work here
                                                                              
fclose( $fp );
unlink( "/var/lock/process.pid" );
                                                                              
?>
jeroenl at see dot below
07-Jul-2004 07:16
lockwang, thanx for pointing out the bug I forgot to update overhere.

I agree (as mentioned) that an old not unlinked lockfile will stop writing, but at least writing will never corrupt old data and if needed you can add a lock clean up.
If you want uncorrupted files and a possible data loss is not a very big problem (like with traffic logging, etc.) this solution does work.

After trying a lot of things (always corrupting files) I use this code now since a few months for loggings and counters on many sites, resulting in (gzipped) files up to 2 Mb, without any corrcupted file or any not unlinked lockfile yet (knock knock...).

A better solution (100% save would be nice :) would be great, but since I haven't seen one around yet ...

If more is needed SQLite (if available) might be an alternative.

Corrected code:
<?
  
function fileWrite($file, &$str, $lockfile = null) {
      
// ceate a lock file - if not possible someone else is active so wait (a while)
      
$lock = ($lockfile)? $lockfile : $file.'.lock';
      
$lf = @fopen ($lock, 'x');
       while (
$lf === FALSE && $i++ < 20) {
          
clearstatcache();
          
usleep(rand(5,85));
          
$lf = @fopen ($lock, 'x');
       }
      
// if lockfile (finally) has been created, file is ours, else give up ...
      
if ($lf !== False) {
          
$fp = fopen( $file, 'a');
          
fputs( $fp, $str); // or use a callback
          
fclose( $fp);
          
// and unlock
          
fclose($lf);
          
unlink($lock);
       }
   }
?>
Joby <god at NOSPAMPLEASE dot greentinted dot net>
30-Jun-2004 07:59
I'm thinking that a good way to ensure that no data is lost would be to create a buffer directory that could store the instructions for what is to be written to a file, then whenever the file is decidedly unlocked, a single execution could loop through every file in that directory and apply the indicated changes to the file.

I'm working on writing this for a flat-file based database.  The way it works is, whenever a command is issued (addline, removeline, editline), the command is stored in a flat file stored in a folder named a shortened version of the filename to be edited and named by the time and a random number.  In that file is a standardized set of commands that define what is to be done to what file (the likes of "file: SecuraLog/index_uid" new line "editline: 14").

Each execution will check every folder in that directory for files and a certain amount of time (I don't know how long, maybe 1-2 seconds) is spent making pending changes to unlocked files.  This way no changes will be lost (i.e. person 1 makes a change at the same time as person 2, and person 1 loses the race by just enough to have their changed version of the file overwritten by person 2's version) and there will be no problems with opening an empty open file.
lockwang
22-Jun-2004 12:28
your code has a mistake , i think you should change[  $lf = @fopen ($lf, 'x');] to [  $lf = @fopen ($lock, 'x');]
and your code has a big problem that if your lockfile is not unlinked for any reason and your function will not work properly. so i think your function is not a good one.
jeroenl at zwolnet dot cNOoSPAMm
21-Apr-2004 06:08
A possible workaround for the missing filelock mechanism. Thanks to Bret below who gave me the idea to use fopen() with the 'x' option.
I use this for logging in gzipped logfiles up to many hundreds of KBs (and some small counter files) since a few weeks without any problems (yet).

But again ... this is not 100% save and will fail with a possible locked file when the script is aborted while the lock is created. This can however - when needed - been solved by creating a lock cleanup e.g. based on filetime.
And writing to the file is not 100% sure: we give up after trying for a while - but what is on the net anyhow :).

If you need something else than adding to the end of a file, use a callback function for rewriting the filecontent.
And you might want to change the loop count and sleep time.
NB: tested (and using) on WinXP, Linux and FreeBSD.

<?
  
function fileWrite($file, &$str, $lockfile = null) {
      
// ceate a lock file - if not possible someone else is active so wait (a while)
      
$lock = ($lockfile)? $lockfile : $file.'.lock';
      
$lf = @fopen ($lock, 'x');
       while (
$lf === FALSE && $i++ < 20) {
          
clearstatcache();
          
usleep(rand(5,85));
          
$lf = @fopen ($lf, 'x');
       }
      
// if lockfile (finally) has been created, file is ours, else give up ...
      
if ($lf !== False) {
          
$fp = fopen( $file, 'a');
          
fputs( $fp, $str); // or use a callback
          
fclose( $fp);
          
// and unlock
          
fclose($lf);
          
unlink($lock);
       }
   }
?>
name at phism dot org
10-Apr-2004 02:01
regarding the first post,
microsleep() will sleep for the specified number of microseconds
06-Apr-2004 12:13
file locking with PHP over NFS shares simply does not work. Any attempt to use it will fail under any race condition.
The only atomic file operation which is usable over NFS is mkdir() which will either create a folder or return a error status...
So on NFS servers, forget flock(), use
@mkdir()
test the result, and if it fails, wait and retry.

Shamely, PHP does not always have short sleeping periods and sleep(1) is too long for most PHP servers that limit execution time (expect to have complaints from users experimenting many 500 errors if the script is too long...)

The bad thing about mkdir() is that it requires you to perform yourself a rmdir() when you're finished with the lock. If the rmdir() is not executed (because of execution time limitation), then the script will be aborted, leaving the locking directory present, and locking your site permanently.

So you also need to check for the date status of the created folder, in a separate NFS operation, to see if the lock is not stale and to clean it (but when this maximum life of the lock has occured), race conditions will happen if several threads or PHP servers will want to drop the staled lock-directory.

There's no simple solution, unless PHP releases a locking function based on mkdir() with AUTOMATIC rmdir() invokation when the PHP script engine terminates after a process or thread interrupt (unless it is killed -9, but a Web Server would normally not kill a script engine with a so brutal kill option).

PHP is most often deployed by free hosting providers on parallel servers over NFS. Using MySQL to coordonate concurrent threads or process is overkill and lacks performance for some pages that have frequent accesses, notably above 500 requests per hour.

PHP developers should really think about providing a working solution that will work on NFS, using mkdir() and rmdir() internally which is the only "magic" atomic operation allwoed there, and that is also performing the best (without requiring any complex client accesses to a SQL server...)
m4v3r at o2 dot pl
04-Apr-2004 06:44
Following is based on below comments. When something goes wrong, script will perform backup of writen data to randomly named file in temp dir.

<?
function safewrite($filename, $data){
  
$rand = microtime();
  
$rand = md5($rand);
  
$temp = fopen("temp/$rand", "w");
  
fwrite($temp, $data);
  
fclose($temp);
  
$otw = fopen($filename, "a+");
   if(
flock($otw, LOCK_EX)){
      
ftruncate($otw, 0);
       if(!
fwrite($otw, $data)) $err = 1;
      
flock($otw, LOCK_UN);
      
fclose($otw);
   } else {
      
$err = 1;
   }
   if(
$err == 1 || (filesize($filename) == 0 && strlen($data) <> 0)){
       die(
"<b>There was an error while writing to $filename. Contact site administrator!</b>");
   } else {
      
unlink("temp/$rand");
   }
}
?>

Hope it helps.
Glip
29-Jan-2004 12:39
If you don't want use secondary lock file while truncating, try this:

<?php

$fp
= fopen("/tmp/lock.txt", "a+");

if (
flock($fp, LOCK_EX)) { // do an exclusive lock
  
ftruncate($fp, 0);
  
fwrite($fp, "Write something here\n");
  
flock($fp, LOCK_UN); // release the lock
} else {
   echo
"Couldn't lock the file !";
}

fclose($fp);

?>
BWO
20-Jan-2004 10:41
Using a secondary file for locking isn't too bad.  You simply surround your actual writes by the flock commands on the locking file.  Because of the blocking, your writes will not execute until you get the exclusive lock. 

<?php
$filename
= "data.txt";
$lock = ".lock";

// Lock the .lock file.
$lock_fp = fopen($lock, "r");
if (!
flock($lock_fp, LOCK_EX)) {
   echo
"locking failed";
}

// Write to the data file.
$fp = fopen($filename, "w");
fwrite( $fp, "word up!");
fclose($fp);

// Release the lock.
flock($lock_fp, LOCK_UN);
?>

Easy as pi.
Klaus Rabenstein von Heilabrück
01-Dec-2003 09:47
I'm not quite sure, whether it is possible to create a effective machanism that ensures file locking by creating something called a "lock-file" (Important: this lock-file isn't thought as file with contents, it only has symbolic meaning if it exists).

There seem to be several reasons for NOT doing the above:
1. You have to check, whether the lock-file exists
1.1 Do something if it exists
1.2 Do something if it doesn't exist
2. Ensure the lock file has rights that prevent messing up with it from another another program
3. and so on...

Hence, it comes forward that this method is very slow and not effective!!!
The argument "flock() is no good because of differing platform based handlings" fails because the methods you need to use the above are at least as much dependable on platforms as flock() is itself.
I'm not commenting on many posters before me for these reasons.

Let's consider you want to write some contents (C) to a file (F). In my opinion a temporay file (T) with a random filename should store the content you want to write to F. After you successfully stored C in T you open a stream to F an close it just after you write C in it. If everything went ok, you can delete T. Otherwise you output a message and encourage the user to try it again. One can use the contents saved in T as optional recovery data.
This way it is highly impossible that another program is messing up with your data because T's name can hardly be guessed, and if flock() works on your system, it is locked as well.
Of course, my method extends the meaning of flock(), but I do not see another way how to deal with problems that might occur when using flock() only.
I hope I wasn't too theoretical.
bret at alliances dot org
30-Oct-2003 05:05
A poster above (below?) has the right idea with writing to a tempory file, then copying. However, achieving the 'right' to use the temp file in the first place is still a race condition issue:

  if (file_exists($temp_filename)) {
     $fd = fopen ($temp_filename, 'w');
  }

is still wrong, there's still a race condition between checking to whether the temp file exists and creating it.

Using the 'x' option for opening is a way to avoid this. With the 'x' option chosen, the fopen will fail (returning FALSE) if the file already exists.

   $fd = fopen ($tmp, 'x');
   if ($blocking == 1) {
     while ($fd === FALSE) {
       clearstatcache();
       usleep(rand(5,70));
       $fd = fopen ($tmp, 'x');
     }
   }
thanatos_101 at yahoo dot com
24-Sep-2003 07:42
flock() is disabled in some linux kernels (2.4 in my case), it
always returns success.  i'm using sysv semaphores to
compensate.

notice : knowing how to use icps and icprm are essential to
debugging semaphore stuff!!!

simple example for an exclusive only lock thingy

     // obtain exclusive lock
   function lock ($filename) {
       // get semaphore key
     $sem_key = ftok($filename, 'LOCK');
       // get semaphore identifier
     $sem_id = sem_get($sem_key, 1);
       // acquire semaphore lock
     sem_acquire($sem_id);
       // return sem_id
     return $sem_id;
   }
                                                                                                                            
     // release lock
   function unlock($sem_id) {
       // release semaphore lock
     sem_release($sem_id);
   }
rob
28-Aug-2003 05:45
locking a file exclusively *and* in non-blocking mode:

if (flock($fh, LOCK_EX | LOCK_NB)) {
  // do sumthin
}
else {
  // fail, report file locked
}
MPHH
05-Jul-2003 02:22
To solve most of the "PHP quit writing my file and messed it up" problems just add:

ignore_user_abort(true);

before fopen and:

ignore_user_abort(false);

after fclose, now that was easy.
joel[at_sign]purerave.com
27-May-2003 08:12
I have found that if you open a currently locked file with 'w' or 'w+' ("file pointer at the beginning of the file and truncate the file to zero length")  then it will not truncate the file when the lock is released and the file available.

Example I used to test it:
a.php:
$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // exclusive lock

$steps = 10;
// write to the file
for ($i=0; $i< $steps; $i++) {
   fwrite($fp, 'a '.time().' test '. $i."\n");
   sleep(1);
}
flock( $fp, LOCK_UN ); // release the lock
fclose( $fp );
----------
b.php:

$fp = fopen( "/tmp/lock.txt", "w+" );
flock( $fp, LOCK_EX ); // exclusive lock

// ftruncate($fp, 0) is needed here! <----

$steps = 5;
// write to the file
for ($i=0; $i< $steps; $i++) {
   fwrite($fp, 'b '.time().' test '. $i."\n");
   sleep(1);
}
flock( $fp, LOCK_UN ); // release the lock
fclose( $fp );

Loading a.php then loading b.php right after will result in:
b 1054075769 test 0
b 1054075770 test 1
b 1054075771 test 2
b 1054075772 test 3
b 1054075773 test 4
a 1054075764 test 5
a 1054075765 test 6
a 1054075766 test 7
a 1054075767 test 8
a 1054075768 test 9

As you can see, b.php does not truncate the file as the w+ would suggest if the file were instantly available. But only moves the pointer to the begining of the file. If b.php was loaded after a.php finished then there would be no "a ..." lines in the file, since it would be truncated.

To fix this you have to add ftruncate($fp, 0) right after the flock.

'r+' and 'a' seem to work fine, though.
hhw at joymail dot com
16-Apr-2003 01:50
Users may find that the following is more useful than flock.

function filelock($operation) {
   $lock_file = "filelock.txt";
   switch($operation) {
       case LOCK_SH: // 1
       case LOCK_EX: // 2
       do {
           clearstatcache();
           usleep(rand(5,70));
       }
       while(file_exists($lock_file));
       $fp = fopen2($lock_file, "wb");
       fwrite($fp, "blah blah blah");
       fclose($fp);
       break;
       case LOCK_UN: // 3
       do {
           clearstatcache();
           usleep(rand(5,70));
       }
       while(!file_exists($lock_file));
       unlink($lock_file);
       break;
       default:
       break;
   }
}
ags one three seven at psu dot edu
13-Mar-2003 12:11
I am not certain how well this works (I've never had problems, but I haven't stress-tested it), but I have been using this solution for performing safe file locking and writing:

   if($fl = fopen($filepath))
       if(flock($fl, LOCK_EX))
           {

           fseek($fl, 0);
           ftruncate($fl, 0);

           fwrite($fl, $whateverdata);

           fflush($fl);
           flock($fl, LOCK_UN);
           fclose($fl);

           }

This "cheats" and opens a file for writing by opening it for append (which doesn't truncate), then acquiring the lock first before performing any truncation or actual write operations.  Thus, if the file cannot be successfully locked, it won't be changed at all.

I usually like to assemble the data to be written in one large buffer first, so the file is open and locked for the shortest time possible.  This is also friendlier to other scripts that are blocking on lock.
derf (at) tamu (dott) edu
25-Feb-2003 05:27
This addresses a few topics posted earlier:

First (to sbsaylors), using a seperate lockfile has problems since you must seperately check the lockfile, then create the lockfile, then perform your action.  In the event of two processes running at the same time, you may wind up in the situation where there is no file and execution proceeds as follows:
A:checks-for-file
B:checks-for-file
A:creates-file
B:creates-file
A:open-file
B:open-file
A:writes
B:writes
... now, both A and B have written different things to the file, which in most systems would be buffered in seperate memory for A and B.  Thus, if A closes first, when B closes its buffer will overwrite A.  If B closes first, A will overwrite B.

Other situations may arise where everything looks ok, because B's entire execution happens before A opens the file.  But then, B will delete the lock file and A will be unprotected.

Second (to the nameless person): flock doesn't exist to guarantee that your program doesn't stop running.  It only makes sure that other programs that bother to flock() won't mess with the file while you're messing with it.

Proper method to do this would be to write into a temporary file, then copy that file over the original in a single operation.  If the execution stops, only the temp file is corrupt.
carl at thep lu se
15-Feb-2003 12:31
[editors note: try out php.net/dio_fcntl]

As flock() doesn't work over NFS (whereas fcntl() does, but there's no PHP interface for that), you may have to provide some sort of backup locking system if for one reason or other you need to use NFS (or VFAT, but as the documentation points out it's antiquated). One way of achieving this is to use a relational database. If you have a table T with an integer column cnt and a _single_ row, you can:
UPDATE T set cnt = cnt + 1 WHERE cnt >= 0
to obtain a shared lock (and of course you need to verify that there was one affected row). For an exclusive lock:
UPDATE T set cnt = -1 WHERE cnt = 0
This scheme has two problems: You rely on the script to be able to release locks before exiting, and there is no way to wait for a lock (except polling). On the whole, if you can use flock() instead, use flock().
04-Sep-2002 01:58
Warning! When PHP is running as a CGI or fast-CGI extension in Apache, flock() does not guarantee that your file updates will complete!!!
If a browser starts a request and interrupts it fast enough by using many F5-key refreshes, Apache will KILL the PHP process in the middle of the update operation (because it will detect an unexpected socket close before the PHP script is complete), leaving an incomplete or truncated file!

A simple PHP script that increments a counter in a text file will demonstrate this: to update the counter, one needs to gain a lock then open the counter file in "a+" mode, rewind it, read it, rewind again, ftruncate it befire writing the new value and closing the counter file and the lock file. Have your script display the new counter value.
Now use your favorite browser on your script, and make many refreshes from the same PC, using the F5-Refresh key, you'll see that sometimes the counter returns to 0, because there's an interruption after the counter file was truncated but before the new counter value was written to the file!

Note that this affects also simple databases updates without rollback logs such as MySQL!

Before updating any data, as a security measure, and if you don't have rollback logs, you should strictly limit the number of update requests per seconds a user can make through your PHP script.

Shame, this requires a database or logging file to enable tracking user activity.

The only solution is to use an auto-backup system (for file-based databases), or a database engine with rollback capability...

I don't know if PHP implements a way to handle gracefully kill signals sent by Apache, so that we can ensure that we can complete a critical update operation.

For this problem, flock() is not a solution!
This is a MAJOR security issue for all sites that use text-file based databases!
geoff at message dot hu
06-Aug-2002 03:10
You should lock the file _before_ opening it, and unlock _after_ closing it. Othervise an another process might be able to access the file between the lock/unlock and the open/close operation.
majer at kn dot vutbr dot cz
28-Feb-2002 08:19
The function flock() I can use only after I open file for blocking writing or reading  to file.
 
But I found, that function flock() do not  block file agains delete by 
$f=fopen($file,"w"); nor agains ftruncate($f,0);

sorry my English
sbsaylors at yahoo dot com
22-Jan-2002 04:39
Well I needed some kind of "lock" in windows, so I just created a lock.file in that directory.  So if anything wants to 'write' to that file (or append, whatever) it checks to see if a lock.file (or whatever name you want to use) is there and if the date of the file is old.  If its old (1 hour) then it deletes it and goes ahead.  If its not old then it waits a random time.  When its finised it deletes the file.

Seems to work alright but I havent tested anything and its only used for maybe a 100 hits a day but... :)

<filetypefnmatch>
 Last updated: Thu, 19 May 2005
Copyright © 2001-2005 The PHP Group
All rights reserved.
This unofficial mirror is operated at: The Server Pages
Last updated: Thu May 19 17:35:34 2005 CDT