Welcome to WebmasterWorld Guest from

Forum Moderators: coopster & jatar k

Message Too Old, No Replies

PHP File writing and avoiding a race condition

A little code audit...



9:42 am on Nov 23, 2006 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member

So, PHP has this issue where you cannot lock a file until you open it, which leaves the door open for a race condition when writing files. I wrote the following code a while back and think it should work (and in fact use it and it works), I just thought I'd post it here for an audit and in case it might be useful for someone to use or I missed something!

* Write a file to the server.
* First open a temp file for writing and acquire a lock.
* Proceed to write, unlock and copy the file from a temp file. If the
* file size is the same, the write worked, clean up and go home. if
* not, clean up and hope it works the next time.
function cms_writeFile($filename, $tempfile, $data) {
$ft = fopen($tempfile, 'w');
if(flock($ft, LOCK_EX)) {
fwrite($ft, $data);
flock($ft, LOCK_UN);
if(copy($tempfile, $filename) && filesize($tempfile) == filesize($filename)) {
return true;
} else { // The whole process failed.
return false;
} else return false;

I pass it a random tempfile name, so at that point I think we avoid any race condition issues, but to be paranoid I check the file size after the copy. If the files are of a different size, I drop them both and hope this works the next time.

An example to use the function:

if(cms_writeFile('test.php', 'randomg-temp.php', 'some data')) {
echo 'written';
} else {
echo 'notwritten';

Seems right, and it works, am I missing anything?


9:47 am on Nov 23, 2006 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member

Gah, right off the bat I'm seeing that I would do better to gen the tempfile name in the function itself, so as to save doing that every time I call the function...


11:07 am on Nov 23, 2006 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member

function cms_writeFile($filename, $data, $tempfile = '') {
if($tempfile == '') {
$tempfile = dirname($filename).'/_'.rand(00000,99999).rand(00000,99999).basename($filename);

So that replaces the top of the function now. You can pass it a tempfile if you want, otherwise it takes care of that for you...


9:34 am on Nov 25, 2006 (gmt 0)

5+ Year Member

Your function avoids a race condition, but does not prevent two copies of it overwriting the same destination file.

It also does not account for the possibility that two editions of the function write files of the same size.

if two calls pass the same tempfile name, then you are back to the original race condition that you were seeking to solve.

Also, if you used

instead of
you would have less clean up to do.

it is not necessary to roll your own tmp file name as

tempnam ( string dir, string prefix )
does this for you.

There is a standard algorithm that can be used to get around your problem, and prevent file overwriting without locks.

if we assume you have a file "lockfile" that already exists and is locked as if it was a mutex, you could get:

function cms_writeFile($filename, $tempfile, $data, $mutex='cms_writeFile') {
$retval=false; //assume failure of function
if(! $tempfile) {
$tempfile = tempnam(dirname($filename),basename($filename));
$fm=fopen($mutex, 'w');// existing file that is always present to be locked as a mutex
if (flock($fm), LOCK_EX) { //Use the mutex to ensure only one write is happening
$ft = fopen($tempfile, 'w');
if(flock($ft, LOCK_EX)) { //Now not essential, but still good practice
fwrite($ft, $data);
flock($ft, LOCK_UN);
if(rename($tempfile, $filename)) { // check for size became unnecessary
$retval=true; // The only path to success
} else { // The whole process failed.
flock($fm,LOCK_UN); // Only unlock mutex when whole atomic action has completed.
return $retval;

This could be trimmed to eliminate the temp file, and in this case, additional locks.
If the revised function is being used to write many different files, then it will form a bottleneck, like a synchronised function in Java.


8:23 am on Nov 27, 2006 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member

Thanks for the reply dragonthoughts,

1. rename - thanks (must read more manual).
2. tempnam - thanks (see #1!).
3. If the revised function is being used to write many different files, then it will form a bottleneck

So, this being a common pattern for file locking, are people accepting of this being a bottleneck?

If I was to use this to cache queries, for example, it could theoretically be much slower for a given request with many queries, no?

Also, looking at the function, while the mutex is locked any requests to cms_writeFile will return false and nothing will be written. So for a given request, multiple calls to cms_writeFile won't queue or fire, they will simply die silently, no?


Featured Threads

Hot Threads This Week

Hot Threads This Month