Filesystem 函数
在线手册:中文 英文
PHP手册

flock

(PHP 4, PHP 5)

flock轻便的咨询文件锁定

说明

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

PHP 支持以咨询方式(也就是说所有访问程序必须使用同一方式锁定, 否则它不会工作)锁定全部文件的一种轻便方法。

Note:

在 Windows 下 flock() 将会强制执行。

flock() 操作的 handle 必须是一个已经打开的文件指针。operation 可以是以下值之一:

flock() 允许执行一个简单的可以在任何平台中使用的读取/写入模型(包括大部分的 Unix 派生版和甚至是 Windows)。如果锁定会堵塞的话(EWOULDBLOCK 错误码情况下),可选的第三个参数会被设置为 TRUE。锁定操作也可以被 fclose() 释放(代码执行完毕时也会自动调用)。

成功时返回 TRUE, 或者在失败时返回 FALSE.

Example #1 flock() 例子

<?php

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

if (
flock($fpLOCK_EX)) { // 进行排它型锁定
    
fwrite($fp"Write something here\n");
    
flock($fpLOCK_UN); // 释放锁定
} else {
    echo 
"Couldn't lock the file !";
}

fclose($fp);

?>

Note:

由于 flock() 需要一个文件指针, 因此可能不得不用一个特殊的锁定文件来保护打算通过写模式打开的文件的访问(在 fopen() 函数中加入 "w" 或 "w+")。

Warning

flock() 不能用于 NFS 以及其它一些网络文件系统。详细资料查看自己操作系统的文档。

在部分操作系统中 flock() 以进程级实现。当用一个多线程服务器 API(比如 ISAPI)时,可能不可以依靠 flock() 来保护文件,因为运行于同一服务器实例中其它并行线程的 PHP 脚本可以对该文件进行处理。

flock() 不支持旧的文件系统,如 FAT 以及它的派生系统。因此,此环境下总是返回 FALSE(尤其是对 Windows 98 用户来说)。

参数

handle

文件系统指针,是典型地由 fopen() 创建的 resource(资源)。

operation

operation is one of the following:

  • LOCK_SH to acquire a shared lock (reader).
  • LOCK_EX to acquire an exclusive lock (writer).
  • LOCK_UN to release a lock (shared or exclusive).

It is also possible to add LOCK_NB as a bitmask to one of the above operations if you don't want flock() to block while locking. (not supported on Windows)

wouldblock

The optional third argument is set to TRUE if the lock would block (EWOULDBLOCK errno condition). (not supported on Windows)

返回值

成功时返回 TRUE, 或者在失败时返回 FALSE.

更新日志

版本 说明
5.3.2 The automatic unlocking when the file's resource handle is closed was removed. Unlocking now always has to be done manually.
4.0.1 The LOCK_XXX constants were added. Prior to that you must use 1 for LOCK_SH, 2 for LOCK_EX, 3 for LOCK_UN and 4 for LOCK_NB

范例

Example #2 flock() example

<?php

$fp 
fopen("/tmp/lock.txt""r+");

if (
flock($fpLOCK_EX)) {  // acquire an exclusive lock
    
ftruncate($fp0);      // truncate file
    
fwrite($fp"Write something here\n");
    
fflush($fp);            // flush output before releasing the lock
    
flock($fpLOCK_UN);    // release the lock
} else {
    echo 
"Couldn't get the lock!";
}

fclose($fp);

?>

Example #3 flock() using the LOCK_NB option

<?php
$fp 
fopen('/tmp/lock.txt''r+');

/* Activate the LOCK_NB option on an LOCK_EX operation */
if(!flock($fpLOCK_EX LOCK_NB)) {
    echo 
'Unable to obtain lock';
    exit(-
1);
}

/* ... */

fclose($fp);
?>

注释

Note:

flock() uses mandatory locking instead of advisory locking on Windows. Mandatory locking is also supported on Linux and System V based operating systems via the usual mechanism supported by the fcntl() system call: that is, if the file in question has the setgid permission bit set and the group execution bit cleared. On Linux, the file system will also need to be mounted with the mand option for this to work.

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()).

Note:

May only be used on file pointers returned by fopen() for local files, or file pointers pointing to userspace streams that implement the streamWrapper::stream_lock() method.

Warning

Assigning another value to handle argument in subsequent code will release the lock.

Warning

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


Filesystem 函数
在线手册:中文 英文
PHP手册
PHP手册 - N: 轻便的咨询文件锁定

用户评论:

ondrej dot nemecek at gmail dot com (15-Mar-2012 11:15)

Notice: On NFS with NFS locking daemon you cannot open file for reading and than lock exklusively:

$rFopened  = fopen($sFile,'r');
$bResult   = flock($rFopened, LOCK_EX);

var_dump($bResult); // returns FALSE

Mixed fopen modes (a+, w+ ...) works with LOCK_EX fine.

mdessaintes at gmail dot com (22-Sep-2011 04:44)

I just spend a long time to understand why write function returns me "0", on a basic file opening and then writing.

I discovered that if you use LOCK_SH and then you write something, that will not work :

<?php
$fp
= fopen('file.txt', 'a');

flock($fp,LOCK_SH);

$written = fputs($fp, 'data');

var_dump($written); // 0 and file is not changed

fclose($fp);
?>

Evan Battaglia (06-Jan-2011 01:13)

LOCK_NB seems to be checked and works fine in Windows, too, in PHP 5.3.3.

For instance, try concurrently running two instances of the following script (via the CLI). The second prints "Didn't quite get the lock..." as expected, whereas w/o the LOCK_NB flag, it just hangs.

<?php
$x
= fopen("flocktest.txt", "w");
if (
flock($x, LOCK_EX|LOCK_NB)) {
    print
"No problems, I got the lock, now I'm going to sit on it.";
    while (
true)
       
sleep(5);
} else {
    print
"Didn't quite get the lock. Quitting now. Good night.";
}
fclose($x);
?>

holdoffhunger at gmail dot com (03-Dec-2010 03:53)

I've been having trouble getting Flock to work when I read a file, delete it, and then output slightly changed information back to the same location.  When deleting with Unlink, there's a very brief period of time where no file exists.  But, if you do an fopen using the "w" mode, it keeps the file in existence, but deletes all of its data when you go to write to it.  That way, the file never actually disappears, and another script accessing the same file with flock won't get a "file doesn't exist" error.

webmaster at bitpush dot com (11-Nov-2010 04:31)

Regarding the change in PHP 5.3.2 with locked files:

Without having studied the PHP source code in detail, the situation appears to be as follows when the PHP function fclose() is called:

Before 5.3.2 PHP would check if the file was locked, then release the lock, and then close the file.

From 5.3.2 PHP just closes the file.

But note, that the operating system releases the lock automatically when the file is closed. Therefore a call to fclose() STILL releases the lock (this is tested with PHP 5.3.2, Linux, x64).

mirco dot babin at gmail dot com (15-Sep-2010 06:10)

Because:
1) flock() is not safe if multiple php sessions are simultaneously locking.
2) fopen( , 'a') is not safe if multiple php sessions are simultaneously appending.
3) usleep and sleep are known for having problems.

I wrote the Weblog function, it's purpose is to append a line to logging. This function handles the concurrency as follows:
- Try to create a lockfile named: $directory . date('Ymd') . $logfile . 1 . lock
- If this fails, try to create lockfile named: $directory . date('Ymd') . $logfile . 2 . lock
- etc. After 100 tries return false.

- When the lockfile is acquired the file named: $directory.date('Ymd').$logfile.1 (or .2 or .3 or .25) is opened or created.
- If created the a "extended log file" header is written.
- Write out the line.
- Close the flie and if created set the correct access rights. (I had some problems creating files on a webserver, I did not see them when I opened a FTP session to the webdirectory. The chmod did the trick for me).

- Remove the lockfile.

There is only one drawback, multiple logfiles are created.
e.g. (executed on 15 september 2010)
    Weblog('./' , 'visit', 'Somebody requested the index page');
Could lead to 100 files, depending how many concurrent php sessions are simultaneously trying to append a logline:
./20100915visit.1
./20100915visit.2
./20100915visit.3
./20100915visit.4
...
./20100915visit.100

This function is donated to the public domain. Maybe you can give me some credit by leaving in the author comment, but it is not required. You may modify this as you wish.
(This function was inspired by the function m_lock_file presented by Kuzma dot Deretuke at gmail dot com)

<?php
function Weblog($directory, $logfile, $message)
{
   
// Created 15 september 2010: Mirco Babin
   
$curtime = time();
   
$logfile = date('Ymd',$curtime) . $logfile;

    if (!isset(
$directory) || $directory === false)
       
$directory = './';
    if (
substr($directory,-1) !== '/')
       
$directory = $directory . '/';
       
   
$count = 1;
    while(
1)
    {
       
$logfilename = $directory . $logfile . '.' . $count;
       
       
$lockfile = $logfilename . '.lock';
       
$lockhandle = false;
        if (!
file_exists($lockfile) || @unlink($lockfile))
           
$lockhandle = @fopen($lockfile, 'xb');
        if (
$lockhandle !== false) break;

       
$count++;
        if (
$count > 100) return false;
    }
   
    if (
file_exists($logfilename))
        {
           
$created   = false;
           
$loghandle = @fopen($logfilename, 'ab');
        }
    else
        {
           
$loghandle = @fopen($logfilename, 'xb');
            if (
$loghandle !== false)
                {
                   
$created = true;
                   
                   
$str = '#version: 1.0' . "\r\n" .
                          
'#Fields: date time c-ip x-msg' . "\r\n";
                   
fwrite($loghandle,$str);
                }
            }
   
    if (
$loghandle !== false)
        {
           
$str = date('Y-m-d',$curtime) . "\t" .
                  
date('H:i:s', $curtime) .  "\t" .
                   (isset(
$_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-') . "\t" .
                  
'"' . str_replace('"', '""', $message) . '"' . "\r\n";
           
fwrite($loghandle,$str);
           
           
fclose($loghandle);
           
            if (
$created) chmod($logfilename,0644); // Read and write for owner, read for everybody else
           
           
$result = true;
        }
    else
        {
           
$result = false;
        }
   
   
fclose($lockhandle);
    @
unlink($lockfile);

    return
$result;
}
?>

dejangex at yahoo dot com (21-Dec-2009 09:47)

Actually, there is no use of the while loop with the usleep. My testing has revealed the following:

<?php
//some code here
flock($file_handle, LOCK_EX) // <- Your code will pause here untill you get the lock for indefinite amount of time or till your script times out
//some code here
?>

This will actually check for the lock without pausing and then it will sleep:

<?php
//code here
while (!flock($file_handle, LOCK_EX | LOCK_NB)) {
 
//Lock not acquired, try again in:
 
usleep(round(rand(0, 100)*1000)) //0-100 miliseconds
}
//lock acquired
//rest of the code
?>

The problem is, if you have a busy site and a lots of locking, the while loop may not acquire the lock for some time. Locking without LOCK_NB is much more persistent and it will wait for the lock for as long as it takes. It is almose guaranteed that the file will be locked, unless the script times out or something.

Consider these two scripts: 1st one is ran, and the second one is ran 5 seconds after the first.

<?php
//1st script
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //lock the file
sleep(10); //sleep 10 seconds
fclose($file_handle); //close and unlock the file
?>

<?php
//2nd script
$file_handle = fopen('file.test', 'r+');
flock($file_handle, LOCK_EX); //lock the file
fclose($file_handle); //close and unlock the file
?>

If you run 1st and then the 2nd script,the 2nd script will wait untill the 1st has finished. As soon as the first script finishes, the second one will acquire the lock and finish the execution. If you use flock($file_handle, LOCK_EX | LOCK_NB) in the 2nd script while the 1st script is running, it would finish execution immediately and you would not get the lock.

daniel AT brightbyte DOT de (08-Sep-2009 08:49)

flock on Solaris is slightly strange: it will fail if you try to get an exclusive lock on a file not opened for writing. That is, for reading files, you MUST use a shared lock. From the Solaris man page for flock:

"Read permission is required on a file to obtain a shared lock, and write  permission is required to obtain an exclusive lock."

In contrast, this is from the Linux man page for flock:

"The mode used to open the file doesn’t matter to flock."

So, beware...

Kuzma dot Deretuke at gmail dot com (06-Aug-2009 12:31)

I use exclusive writing to replace standard flock():

<?php
// get/set lock file name
function m_lock_file( $format = null ) {
    static
$file_format = './%s.lock';
   
    if (
$format !== null) {
       
$file_format = $format;
    }
   
    return
$file_format;
}

// acquire/check/release lock
function m_lock( $lockId, $acquire = null ) {
    static
$handlers = array();
   
    if (
is_bool($acquire)) {
       
$file = sprintf(m_lock_file(), md5($lockId), $lockId);
    }
   
    if (
$acquire === false) {
        if (isset(
$handlers[$lockId])) {
            @
fclose($handlers[$lockId]);
            @
unlink($file);
            unset(
$handlers[$lockId]);
        } else {
           
trigger_error("Lock '$lockId' is already unlocked", E_USER_WARNING);
        }
    }
   
    if (
$acquire === true) {
        if (!isset(
$handlers[$lockId])) {
           
$handler = false;
           
$count = 100;
            do {
                if (!
file_exists($file) || @unlink($file)) {
                   
$handler = @fopen($file, "x");
                }
                if (
false === $handler) {
                   
usleep(10000);
                } else {
                   
$handlers[$lockId] = $handler;
                }
            } while (
false === $handler && $count-- > 0);
        } else {
           
trigger_error("Lock '$lockId' is already locked", E_USER_WARNING);
        }
    }
   
    return isset(
$handlers[$lockId]);
}
?>

Usage sample:

<?php
$lockId
= "qq";

m_lock($lockId, true);
if (
m_lock($lockId)) {
    echo
"locked";

   
// here you can perform any thread-safe operations
   
usleep(300 * 1000);

   
m_lock($lockId, false);
} else {
    echo
"not locked";
}

?>

jbatka01 at gmail dot com (27-Apr-2009 09:54)

For those who just want to check if a file is available for locking and return immediately (without blocking), use the following syntax:

<?php
$file
= fopen('file.txt', 'w');

if(
flock($file, LOCK_EX | LOCK_NB)){
    echo
'Got lock, continue writing to file';
   
// Code to write to file
}else{
    echo
'File is locked by another process, aborting writing';
   
// Couldn't obtain the lock immediately
}
?>

This is a quick, easy way to determine if another process is using the file or not, without blocking your script.

Fernando Gabrieli fgabrieli at gmail (12-Feb-2009 07:06)

When writing to a file, you should avoid using w+ because it would erase the contents of the file before locking

If you need to write the complete file again you could use the following instead:

<?php
$fp
= fopen('yourfile.txt', 'a') ;

if (
flock($fp, LOCK_EX)) {
   
ftruncate($fp, 0) ; // <-- this will erase the contents such as 'w+'
   
   
fputs($fp, 'test string') ;
   
   
flock($fp, LOCK_UN) ;
}

fclose($fp) ;
?>

Best,
Fernando Gabrieli

yesmarklapointe at hotmail dot com (05-Dec-2008 12:35)

Perhaps not what you are looking for, but seems to me that  to avoid a race condition you have to end the race, that is, stop allowing multi-threaded or parallel processes to do the file manipulation part of the overall application. Put the data outside the competing scripts and let a separate process handle the transfer or replacement or storage in a deterministic way … if you can wait that long for it. For example,  why not have a chron job process temporary files in a special directory, making their individual changes to the ‘real’ file based on something simple like which one is stored first in this special directory. All of your scripts have to put their file change requests into this directory by a sufficiently random naming scheme … but maintaining an incremental aspect to the naming so they are in order for processing. In this example, besides the chron job, no other script should have more than read access to the real file.

administrator at proxy-list dot org (23-Nov-2008 10:14)

Hello,

There is couple of reasons why EVERY software developer should avoid its usage if it is possible. My own server was crashed couple of times because of flock (including hours of down time). So you have to think twice before you will decide to use it.

After a while I have found the problematic situations. For my surprise it was not just badly written PHP scripts, it was some unclear bugs in FreeBSD. In some case, when more than 20 requests per second try to do exclusive flock on the same file, the scripts stuck ;) and they were using 100% of CPU (only Apache’s reboot or manual kill could stop them).

Soon after I have found nice solution (thanks arne at bukkie dot nl for good suggestion). I want to share it with you:

<?php
// waiting until file will be locked for writing (1000 milliseconds as timeout)
if ($fp = fopen($fileName, 'a')) {
 
$startTime = microtime();
  do {
   
$canWrite = flock($fp, LOCK_EX);
   
// If lock not obtained sleep for 0 - 100 milliseconds, to avoid collision and CPU load
   
if(!$canWrite) usleep(round(rand(0, 100)*1000));
  } while ((!
$canWrite)and((microtime()-$startTime) < 1000));

 
//file was locked so now we can store information
 
if ($canWrite) {
   
fwrite($fp, $dataToSave);
  }
 
fclose($fp);                                 
}
?>

I have added simple timeout value for flock. Case flock was not obtained within N seconds - writing will be skipped, instead of waiting and causing server’s high CPU load.

With best regards

Owner of http://proxy-list.org

tinymountain at nospam dot gmail dot com (31-Jul-2008 09:36)

Here's a handy class to allow retrying a write with flock a set number of times. If it can't flock, it will sleep for a brief random interval and try again. If you have a lot of concurrent writes going on, you can use it to avoid data corruption.

<?php
class SafeWriter
{
   
// suggested mode 'a' for writing to the end of the file
   
public static function writeData($path, $mode, $data)
    {
       
$fp = fopen($path, $mode);
       
$retries = 0;
       
$max_retries = 100;

        if (!
$fp) {
           
// failure
           
return false;
        }

       
// keep trying to get a lock as long as possible
       
do {
            if (
$retries > 0) {
               
usleep(rand(1, 10000));
            }
           
$retries += 1;
        } while (!
flock($fp, LOCK_EX) and $retries <= $max_retries);

       
// couldn't get the lock, give up
       
if ($retries == $max_retries) {
           
// failure
           
return false;
        }

       
// got the lock, write the data
       
fwrite($fp, "$data\n");
       
// release the lock
       
flock($fp, LOCK_UN);
       
fclose($fp);
       
// success
       
return true;
    }
}
?>

John dot wellesz at teaser dot fr (15-Apr-2008 01:59)

I just want to add a note about making atomic lock on NFS, there is only two
ways:

- 1 (the most robust but the most complicate) - It's to use link() to create a
  hard link to a file you want to lock (on the same FS of course).
  (On most NFS implementations, Link() is atomic)

Once you created a hard link (not a symbolic link), with a unique randomly
generated name, call stat() on it and count the number of link (nlink), if there
is only 2 then the file is locked.

If there is more than two you have to unlink() the link you just created and
create a new one with a new unique name (else NFS will use its cache and stat
will return wrong data) then call stat() on the new link and test the number of
links again, repeat this operation until you get the lock.

You have to use usleep() between the link() attempts with a fixed + random
sleep value to avoid dead lock situations (link() and unlink() may be atomic
but not instantaneous)

Also note than when you unlink a file through NFS, if NFS think that the file
is still in use, it will create a .nfs link to this file until it realizes the
file is no longer in use... A wrong timing could generate thousands of those
files and a deadlock situation.  Because of this when a deadlock situation
occurs or if your stat() command returns a very high number of links, you have
to look for .nfs file in the same directory you created your links and unlink
all the .nfs file you find (sometimes NFS take its time to remove them)

- 2 (the simplest) - the second method is to use a lock server and lock daemons
  on each client that will forward lock request to the server... (this is more
dangerous than the first method because the daemons may be killed...)

Here is for reference the function I created to make atomic locks through NFS
(this function is in production since at least 4 years now), it's just for
reference because it uses many external functions to do its job but you can see
the principle:

http://pastey.net/85793

admin ifyouwantblood de (24-Dec-2007 03:05)

besides from what the manual says about locking a file opendend in w or w+ and using a special lock file for these cases, you should simply truncate the file yourself with ftruncate() after writing:

<?php

$data
='some data';
$handle=fopen('file','r+');
flock($handle,LOCK_EX);
fwrite($handle,$data);
ftruncate($handle,ftell($handle));
flock($handle,LOCK_UN);
fclose($handle);

?>

now the file will have the size of $data without opening the file in w mode but with a lock on the file.

to the previous writers jpriebe and mallory:
of course the lock is lost in this case, but thats simply because the file is closed by PHP. and closing the file means unlocking it (same as when you use fclose() yourself).

mallory dot dessaintes at gmail dot com (19-Dec-2007 03:56)

I have noticed that if you change the value of your fopen ressource, the lock is working no longer..

<?php

$fo
= fopen('lockfile.txt','a');

flock($fo,LOCK_EX);

$fo = '';

// Lock is disable

?>

candide at idees-et-solutions dot fr (07-Dec-2007 11:48)

Just a comment about the last method to lock files using filemtime().
What if   filemtime($fp[1]) == $fp[3]   because somebody modified the file less than 1s after the value of $fp[3] was picked up?
Then this modification will be lost...?

This system to lock files is made to prevent problems when two modifications are so close that they can interfere, so the case "less than 1s" will often happen?

However, lose some modifications is better than spoil all the file...

Antti Haapala (06-Oct-2007 12:30)

Further information on flock: The system is not restarted if a signal is delivered to the process, so flock will happily return false in case of SIGALRM, SIGFPE or something else.

Antti Haapala (06-Oct-2007 11:41)

The supplied documentation is vague, ambiguous and lacking, and the user comments contain erroneous information! The flock function follows the semantics of the Unix system call bearing the same name. Flock utilizes ADVISORY locking only; that is, other processes may ignore the lock completely; it only affects those that call the flock call.

LOCK_SH means SHARED LOCK. Any number of processes MAY HAVE A SHARED LOCK simultaneously. It is commonly called a reader lock.

LOCK_EX means EXCLUSIVE LOCK. Only a single process may possess an exclusive lock to a given file at a time.

If the file has been LOCKED with LOCK_SH in another process, flock with LOCK_SH will SUCCEED. flock with LOCK_EX will BLOCK UNTIL ALL READER LOCKS HAVE BEEN RELEASED.

If the file has been locked with LOCK_EX in another process, the CALL WILL BLOCK UNTIL ALL OTHER LOCKS have been released.

If however, you call flock on a file on which you possess the lock, it will try to change it. So: flock(LOCK_EX) followed by flock(LOCK_SH) will get you a SHARED lock, not "read-write" lock.

administrator at proxy-list dot org (30-Apr-2007 01:30)

Hello,

I want to give an example how to lock file with two or more flags (for example reading and writing). IMPORTANT: each locking should be done separately, the correct way of using flock() is:

<?php
flock
($fp, LOCK_EX);
flock($fp, LOCK_SH);
?>

and NOT like these:

<?php
flock
($fp, LOCK_EX and LOCK_SH);
flock($fp, LOCK_EX or LOCK_SH);
flock($fp, LOCK_EX + LOCK_SH);
?>

Furthermore if someone has not pay attention to function’s description - flock does not lock any file in the right way. The file is still accessible for reading/writing, in other words these functions: file(), file_get_contents() and even fopen($file, ‘r’) will ignore the lock.

I think PHP mechanism works something like this: as soon as file lock was successful, the function flock() writes somewhere (in its own “DB” for example) that file handle is locked with some flag and nothing more. It is up to the developer to check if file is locked or not before doing any operations.

Hope this post makes clear the flock() function’s working principles.

Regards

Vitali Simsive

==

During test I have discovered that if you have locked file for writing (LOCK_EX) both read and write will not be accessible from other scripts. In case of read locking (LOCK_SH) only writing will not be accessible for other PHP scripts but they will be able to read file simultaneously.

LemonJuice (09-Apr-2007 08:37)

Hi,

The discussions below address flock() in the context of managing integrity of file contents as well as the context of using flock() in combination with a dummy file to generally establish agreement on the access state of some other object. The following addresses the latter.

I use this as a replacement for LOCK TABLES because during some transactional update statements I require the contents of other tables to freeze and transactions and tablelocks don't mix in mySQL / InnoDB.

<?php

class ReadWriteLock
{
    const
LOCK_PATH = "locks";
    public static function
Aquire($ID, $LockType = LOCK_SH, $WouldBlock = TRUE)
    {
       
// Make sure the file exists and we have it opened.
        // We don't care about writing to the file. We just need a file reference that flock() can work on.
        // Also, on an OS level all thread's are sharing this file. We don't do access control in relation to this file.
        // So let's assume first that it already exists.
       
$FileName = self::LOCK_PATH."/lock_$ID.lck";
        if((
$Resource = @fopen($FileName, "r")) === FALSE)
           
// Ok, so this is the first time a thread acquires a lock to this $ID. Let's create the file.
           
if(($Resource = @fopen($FileName, "w")) === FALSE)
            {
               
// Ok, perhaps some thread created it between the two ifs. This class does not delete the file so it should now be there.
               
if(($Resource = @fopen($FileName, "r")) === FALSE)
                    return
FALSE;
            }
            else
            {
               
// #REF 1
                // Ok, it exists now. Just for solidarity and prevention of whatever OS hickups we can possibly have
                // I want this thread to open it in r mode too.
               
if(fclose($Resource) === FALSE)
                    return
FALSE;
                if((
$Resource = @fopen($FileName, "r")) === FALSE)
                    return
FALSE;
            }
       
// And this is really where the locking takes place.
       
if(flock($Resource, $LockType, $WouldBlock)) return $Resource;
       
fclose($Resource);
        return
FALSE;
    }

    public static function
Release($Resource)
    {
        if(
fclose($Resource)) return TRUE;
        return
FALSE;
    }
}

// Entering critical section
if(($Lock = ReadWriteLock::Acquire("metadata")) === FALSE)
    die(
"Failed to either create or acquire the lock.")

// Construct SQL statements from meta tables
// Execute constructed SQL statements agains data tables

// Leaving critical section
if((ReadWriteLock::Release($Lock)) === FALSE)
    die(
"Failed to release the lock.");
?>

A few notes about this:

- As you can see, this just creates 'a' file to use as a reference for flock() to work on. My assumption here is that the operating system uses semaphores internally to implement Flock(). Of that, I am not sure however and I would appreciate any validation from an expert.

- The problem of the existence of the lockfile is solved by simply not deleting them. Given that they are all 0-byte files in a specified folder and that they are only of a limited amount makes it something that works for my solution. Alternatively you could touch() the lockfile upon a succesful flock() and use a cron job to delete any files that have not been touched since, say, a day (or at least for the duration of the session timeout setting of your webserver). That would introduce a race condition for access on the actual file though which I prefer to exclude from the above.

- flock() implements a low priority exclusive lock. This means that once the resource is locked in a shared mode, exclusive locks may be delayed indefinately if (and only if) a continuous abundance of shared lock requests come in so that every thread releases his shared lock after another thread has already gained shared access. For me, this is an ussue and I would appreciate any references to establish a high priority exclusive lock.

Good luck,

Juice
...Tastes like more!

Will Reiher (28-Mar-2007 12:52)

I've been testing a few custom file access functions but I always liked the simplicity of file_get_contents(). Of course it doesn't seem to respect any file locks created with flock(). I created the function below to wrap around file_get_contents() so it can support locked files. It's an odd way of doing it but it works for me.

<?php
function flock_get_contents($filename){

   
$return = FALSE;

    if(
is_string($filename) && !empty($filename)){
        if(
is_readable($filename)){
            if(
$handle = @fopen($filename, 'r')){
                while(!
$return){
                    if(
flock($handle, LOCK_SH)){
                        if(
$return = file_get_contents($filename)){
                           
flock($handle, LOCK_UN);
                        }
                    }
                }
               
fclose($handle);
            }
        }
    }
   
    return
$return;
}
?>

korostel at newmail dot ru (29-Jan-2007 11:22)

In order to prevent access to some counter file we can use another file as a flag instead of flock().
We change flag's file mode to '400' each time we want to change the counter, and set it back to '600' at the end.
If the mode of the flag's file was already changed by another process, we make delayed loop till its mode is set to writable again.
The page is refreshing every second so the results can be seen by opening it in two or three browser windows. I tested the script meny times and still my counter is safe.
Hope this will help.
--------------START------------------
<?php
header
("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
header("Last-Modified: ".Date("D, d M Y H:i:s")." GMT");
define('COUNT_FILE','count.txt'); //Our counter file
define('LOCK_FILE','lock.txt'); //Our lock file.

function read_write () {
   
//making our lock file nonwritable
   
chmod(LOCK_FILE, 0400);
   
//reading
   
clearstatcache();
   
$fpr = fopen(COUNT_FILE, "r");
   
$count=fread ($fpr, filesize (COUNT_FILE));
   
fclose($fpr);
   
$count++;
   
//writing
   
$fpw = fopen(COUNT_FILE, "w");
   
fwrite($fpw,$count);
   
fclose($fpw);
   
//pause the script just to see how it works
   
sleep(1);
   
//making our lock file writable again
   
chmod(LOCK_FILE, 0600);
    return
$count;
}
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><title>Untitled</title><meta http-equiv="Refresh" content="1"></head><body>
<?php
clearstatcache
();
//Check the mode of our lock file
if(is_writable(LOCK_FILE)) {
    print
'is unlocked <br>';
   
//make changes and show the results
   
print 'changed to '.read_write().'<br>';
}else{
    print
'is locked: ';
   
//make loop
   
while(!is_writable(LOCK_FILE)) {
        print
'pause... ';
       
usleep(rand(5,999));
    }
   
//make changes and show the results
   
print '<br>changed at last to '.read_write().'<br>';
}
?>
</body></html>
--------------END------------------

jerry at gh33 dot org (14-Aug-2006 05:30)

Indeed, flock() will not work reliably when the underlying filesystem is NFS. The proper way to perform file locking, in this case, would be to use PHP's link() function. From the Linux man page of open():

       O_EXCL When used with O_CREAT, if the file  already  exists  it  is  an
              error  and  the open will fail. In this context, a symbolic link
              exists, regardless of where its points to.  O_EXCL is broken  on
              NFS file systems, programs which rely on it for performing lock-
              ing tasks will contain a race condition.  The solution for  per-
              forming  atomic  file  locking  using  a lockfile is to create a
              unique file on the same fs  (e.g.,  incorporating  hostname  and
              pid),  use  link(2)  to  make  a link to the lockfile. If link()
              returns 0, the lock is successful.  Otherwise,  use  stat(2)  on
              the  unique  file to check if its link count has increased to 2,
              in which case the lock is also successful.

dranger AT export dash japan dot com (17-Feb-2006 06:03)

Also note that if you want to truncate a file, but make sure it's locked first, you DON'T need to use a separate lock file like the directions say. Use this instead:

<?php

$f
=fopen("file", "r+");
flock($f, LOCK_EX) or die("Error! cant lock!");
ftruncate($f, 0);
fwrite($f, $stuff);
fclose($f);

?>

But if you open a file with "w" or "w+" you WILL blow it away before you can lock it.

marc dot vanwoerkom at fernuni-hagen dot de (15-Feb-2006 10:00)

I ran into a loop because I just checked for true (= you got the lock) as return value of flock() and tried again when I got a false.

<?php
   
function naive_wait_for_file($fp) {
        while (
true) {
            if (
flock($fp, LOCK_EX)) {
                return;
            }
           
$k = rand(0, 20);
           
usleep(round($k * 10000));  # k * 10ms
       
}
    }
?>

Unfortunately in one case the $fp I put in was invalid, so I always got false and got stuck.
Lesson: check if your $fp is valid before entering the loop, or look closer if you get a false.

<?php
   
function wait_for_file($fp) {
        if (
$fp === false) {
            return;
        }
        while (
true) {
            if (
flock($fp, LOCK_EX)) {
                return;
            }
           
$k = rand(0, 20);
           
usleep(round($k * 10000));  # k * 10ms
       
}
    }
?>

administrator at proxy-list dot org (29-Dec-2005 04:12)

Hello guys,

I want to shear one good trick, it was no invented by me but it is very useful with flock. This technology was used by team who invent Ethernet as my tutor Pitter Timothy teach me. I want to say thank you.

If you have a lot of scripts about 1000 which possible try to write something in file you need to lock file before starting writing. So you should use something like this:

<?php
        $fp
= fopen($logFileName, 'a');
       
$canWrite = false;
       
//Waiting until file will be locked for writing
       
while (!$canWrite) {
         
$canWrite = flock($fp, LOCK_EX);
        }
       
//file was locked so now we can store information
       
fwrite($fp, $toSave);
       
fclose($fp);
?>

but during testing I have find out what some times script have to many collisions, and during 10 seconds can not write anything. It happened because some scripts try simultaneously. If file was busy they all will wait same time. So I use the same technology like guys who invent first simple Ethernet use in case of package collision. I put random millisecond sleep. You can not imagine but script start working 3 times quicker!

<?php
        $fp
= fopen($logFileName, 'a');
       
$canWrite = false;
       
//Waiting until file will be locked for writing
       
while (!$canWrite) {
         
$canWrite = flock($fp, LOCK_EX);
         
//Sleep for 0 - 2000 miliseconds, to avoid colision
         
$miliSeconds = rand(0, 20); //1 u = 100 miliseconds
         
usleep(round($miliSeconds*100000));
        }
       
//file was locked so now we can store information
       
fwrite($fp, $toSave);
       
fclose($fp);
?>

By the way I have no idea which diapason is better, but 0 - 1000 is not enough.

Hope it will help somebody

Best regards in your projects

Vitali Simsive

damasta at onwebworx dot net (10-Aug-2005 04:28)

just wanted to say that you will most likely fail if you use a separate lock file together with register_shutdown_function.

my script did some different actions... resizing pictures, rotating them and this stuff. it needed a "database" file to get the correct file locations. this database file also stored some flags. and of course the script had to save that file when it was done.

because of my script exited on many different points depending on the action i used register_shutdown_function to save the file. it wanted to use a locking system to be sure the script doesn't overwrite the data another process had written into it some microseconds before. i was running on windows 2000 and apache2 on my developing machine, and flock always returned true for some reason... so i used a separate lock file. the script looked for it at the beginning and exited if it was found. otherwise it created it. but this file had to be deleted at the end. i put the unlink command into the registered shutdown-function but it never deleted the file. i tried clearstatcache and some other stuff but it didn't help.

maybe this helps someone.

pentek_imre at mailbox dot hu (31-Oct-2004 11: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 (29-Sep-2004 12: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;
}

?>

rudy dot metzger at pareto dot nl (08-Sep-2004 11: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" );
                                                                               
?>

Joby <god at NOSPAMPLEASE dot greentinted dot net> (01-Jul-2004 01: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.

m4v3r at o2 dot pl (05-Apr-2004 12: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.

<?php
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 06: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 (21-Jan-2004 04: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.

rob (28-Aug-2003 11:45)

locking a file exclusively *and* in non-blocking mode:

<?php
if (flock($fh, LOCK_EX | LOCK_NB)) {
 
// do sumthin
}
else {
 
// fail, report file locked
}
?>

joel[at_sign]purerave.com (28-May-2003 02: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:
<?php
// 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 );
?>

----------
<?php
// 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.

ags one three seven at psu dot edu (13-Mar-2003 06: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:

<?php
   
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.

carl at thep lu se (15-Feb-2003 06: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 07: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 09: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.