note 62638 deleted from function.flock by danbrown
| From: | danbrown@php.net | Date: | Mon, 18 May 2009 00:38:44 +0000 |
| Subject: | note 62638 deleted from function.flock by danbrown | ||
| References: | 1 | Groups: | php.notes |
| Request: | Send a blank email to php-notes+get-155402@lists.php.net to get a copy of this message | ||
Note Submitter: dranger at export dash japan dot com
----
Re: Niels Jaeckel
The code posted unfortunately does NOT work in race conditions, because even though it is
improbable, there is still a chance the 'mutex' will fail and you will encounter a
situation where two programs have a 'lock' for the same file at the same time. It is
improbable, but in a busy system with perhaps many processes trying to open one file or perhaps two
processes opening the file several times, the probability rises and you will most certianly get
strange bugs at strange times.
Even though it looks quite improbable, there are two problems:
1) The extra ifs do not add any extra precautions. You might as well usleep for a few microseconds
because only the last one matters.
2) Even though it looks like a single atomic command, if you look at the PHP source code, touch is
relatively complicated. It gets the time from the computer, checks the base directory, etc.,etc.
This means that there will be times that this lock fails, no matter how improbable. What makes a
race condition so nasty is that it happens only once every so often, making it nearly impossible to
debug and really annoying.
*** The only safe way to implement locks is with flock or some other locking mechanism *outside* of
PHP. ***
(caveat: there may be an alternate locking mechanism in PHP i don't know about, but you cannot
make your own)
To put this to rest, make a PHP script using the functions described in the previous post. Then, add
this code to the file:
<?php
/*** test.php ***/
// lock and unlock function defs go here
while(1) {
if(lock("test")) {
$f=fopen("importantfile", "w") or die;
$pid=getmypid();
$string="Important Information! From $pid";
fwrite($f, $string);
fclose($f);
$check=file_get_contents("importantfile");
if($check != $string) {
echo "THIS LOCK FAILED!\\n";
}
unlock("test");
}
}
?>
Then run this script from the command line - it will loop forever happily. Then, *while that script
is running*, run the same script again while the first one is still going. In a UNIX environment you
can do this by typing:
> php test.php &
> php test.php &
You will probably see this:
Warning: unlink(test.lock): No such file or directory in lock.php on line 29
THIS LOCK FAILED!
Warning: touch(): Utime failed: No such file or directory in lock.php on line 19
a lot of times.
This means the lock has failed :)
In fact, if you ever think you have invented a clever way to lock a file, test it first in this
while loop. Just replace lock and unlock with your function and rearrange the code so it makes
sense. Then run it and see if it fails.
Note that flock() passes this test beautifully.