5

I want to create a simple method to write a string to a log file, and since the file might be written by different web server processes at the same time, file locking is needed for atomicity.

e.g.

function log_to_file($message)
{
   $fp = fopen("/tmp/lock.txt", "r+");

   while (!flock($fp, LOCK_EX)) {
     sleep(1); // Sleep for 1 second and try again
   }

   fwrite($fp, $message);
   fflush($fp);            
   flock($fp, LOCK_UN);    // release the lock

   fclose($fp);
}

Any problem with the above code?

Ryan
  • 10,041
  • 27
  • 91
  • 156
  • 4
    No you shouldn't. `flock()` blocks unless you tell it not to, which you didn't. No need for the loop. If it fails when you didn't specify `LOCK_NB`, it probably isn't going to work at all. However, file locking like this is not good for concurrency - multiple processes will queue waiting for a lock, and the locks are not guaranteed to be served in the order in which they were requested. Whatever you are doing, a database is almost certainly the tool for the job. – DaveRandom Mar 19 '13 at 17:13
  • 1
    http://stackoverflow.com/a/15305368/889949 – DaveRandom Mar 19 '13 at 17:18
  • 1
    @DaveRandom, so how often will `flock` do the check? I use loop and sleep in order reduce the chance of frequent checking under high load. – Ryan Mar 20 '13 at 15:20
  • It will do the check exactly once - because it will block until it successfully acquires a lock. Blocking means that the execution of your script will halt until a lock is acquired. If acquiring a lock fails, it will most likely fail on subsequent calls as well - so you would end up with an infinite loop. If you want to use this file locking approach, simply `if (!flock($fp)) { /* handle error */ }` is sufficient. – DaveRandom Mar 20 '13 at 15:51
  • @DaveRandom "Blocking means that the execution of your script will halt until a lock is acquired" - so that means, if I request a LOCK on a file, that lock will eventually be granted to me (just a matter of time)? – Sebastian Jul 11 '13 at 20:09
  • 1
    @Sebastian That's not guaranteed. Your request will be serviced eventually, but whether your lock request is successful or not should be ascertained by inspecting the return value. For example, if the process that holds the lock that is blocking your script deletes the file before releasing the lock then your request will fail. – DaveRandom Jul 11 '13 at 21:37
  • @DaveRandom thank you for your immediate response. I am looking for a definite answer to solving a race condition when a webserver is serving a php file which another process is trying to unlink at the very same moment - http://stackoverflow.com/questions/17591772/. Any ideas? Can I loop over unlink(file) for as long as unlink() returns false? Good idea or not? (: – Sebastian Jul 11 '13 at 21:45
  • 1
    @Sebastian That's probably not a great idea (at least, not without a timeout), because if the unlink() call is failing for another reason (e.g. someone has manually altered the permissions so you can't delete it) then your script would loop on this forever, probably consuming a lot of CPU cycles. `flock($fp, LOCK_EX)` is probably you best bet - but I wouldn't `unlink()` unless you don't plan to replace the file. Instead you should `flock()`, `ftruncate()` and write the new contents in one atomic operation. – DaveRandom Jul 11 '13 at 22:10
  • @DaveRandom thank you so much for your elaborate answer! I've been doing some research on this and have not found a definite pattern / rule to apply to my problem of concurrent unlink() and fwrite() operations... I'd never loop over something indefinately, I'd set a loop to try 3-5 times maybe and then give up (: Thanks again. Will have do to some further research I guess. – Sebastian Jul 12 '13 at 10:00

1 Answers1

0

I'm using error_log('My log message',3,$logFile); for the same purpose. I reaching up to 300 write requests per second on peek time, for about two months and still no any problems with file locking.

Denis O.
  • 1,841
  • 19
  • 37
  • Thanks, after some research, since `error_log` is even better as no locking is needed: https://bugs.php.net/bug.php?id=40897 – Ryan Mar 20 '13 at 15:24