File Locking in PHP

file locking in php

You should put a lock on the file:

$fp = fopen($updateFile, 'w+');
if (flock($fp, LOCK_EX)) {
fwrite($fp, 'a');
flock($fp, LOCK_UN);
} else {
echo 'can\'t lock';
}

fclose($fp);

Read and write to a file while keeping lock

As said, you could use FLock. A simple example would be:

//Open the File Stream
$handle = fopen("file.txt","r+");

//Lock File, error if unable to lock
if(flock($handle, LOCK_EX)) {
$count = fread($handle, filesize("file.txt")); //Get Current Hit Count
$count = $count + 1; //Increment Hit Count by 1
ftruncate($handle, 0); //Truncate the file to 0
rewind($handle); //Set write pointer to beginning of file
fwrite($handle, $count); //Write the new Hit Count
flock($handle, LOCK_UN); //Unlock File
} else {
echo "Could not Lock File!";
}

//Close Stream
fclose($handle);

Does php's file_get_contents ignore file locking?

flock is relatively independent of the file operations, you can even use fopen on a locked file. You as developer are responsible for checking/using flock everywhere you require a lock.

But yes in that regard it's true that file_get_contents has no build-in way to acquire a read lock when reading the file. So the workaround would be the way to go.

file_put_contents allows you to get a lock for writing though.

PHP flock() alternative

There is no alternative available to safely achieve the same under all imaginary possible circumstances. That's by design of computer systems and the job is not trivial for cross-platform code.

If you need to make safe use of flock(), document the requirements for your application instead.

Alternatively you can create your own locking mechanism, however you must ensure it's atomic. That means, you must test for the lock and if it does not exists, establish the lock while you need to ensure that nothing else can acquire the lock in-between.

This can be done by creating a lock-file representing the lock but only if it does not exists. Unfortunately, PHP does not offer such a function to create a file in such a way.

Alternatively you can create a directory with mkdir() and work with the result because it will return true when the directory was created and false if it already existed.

Why are lock files used in PHP instead of just counting the processes?

Based on comments here and my own observations, I've composed a list of pro's and con's of both approaches:

flock method:

pros:

  • More compatible across operating systems
  • No knowledge of bash required
  • More common approach, lots of examples
  • Works even with exec() disabled
  • Can use multiple locks in a single file to allow different running "modes" of the same file at the same time

cons:

  • It's not definite. If your lock file is deleted by an external process / user, you could end up with multiple processes. If you're saving the lock file in the /tmp directory, that's a valid possibility, since everything in this directory is supposed to be "temporary"
  • Under certain circumstances, when a process dies unexpectedly, the file lock can be transferred to an unrelated process (I didn't believe this at first, but I found instances of it happening (although rarely) across 200+ unix based systems, in 3 different operating systems)

exec("ps -C...") method

pros:

  • Since you're actually counting the processes, it will work everytime, regardless of the state of file locks, etc.

cons:

  • Only works in linux
  • requires "exec" to be enabled
  • If you change the name of your script, it could cause double processes (and make sure your script name isn't hard-coded in the code)
  • Assumes that your script only has one running "mode"

EDIT: I ended up using this:

if (exec("pgrep -x " . $scriptName . " -u ". $currentUser . " | wc -l") > 1)
{
echo $scriptName . " is already running.\n";
exit;
}

... because ps doesn't allow you to filter on the owner of the process in addition to the process name, and I wanted to allow this script to run multiple times if a different user was running it.


EDIT 2:

... So, after having that running for a few days, it's not perfect either. Somehow, the process started up multiple times on the same machine under the same user. My only guess is that there was some issue (ran out of memory, etc) that caused the pgrep to return nothing, when it should have returned something.

So that means that NEITHER the flock method and the counting process methods are 100% reliable. You'll have to determine what approach will work better for your project.

Ultimately, I'm using another solution that stores the PID of the current task in a "lock" file, that's not actually locked with flock. Then, when the script starts up, checks if the lock file exists, and if it does, gets the contents (PID of the last time the script started up) Then, it checks if it's still running, by comparing the /proc/#PID#/cmdline contents with the name of the script that's running.

What happens to PHP file lock if the script times out or is terminated while the lock was on?

I just made a couple of tests on the following shared server:

PHP Version 5.4.34
Linux 3.12.35.1418868052 #1 SMP x86_64

And my conclusion is that file locks are released automatically once the script finishes running, even in case of a fatal error, a timeout or out-of-memory fault that terminates the script, or if I comment out flock($file, LOCK_UN); function.

Check if file is locked by concurrent process

I wrote a small test that uses sleep() so that I could simulate concurrent read/write processes with a simple AJAX call. It seems this answers both questions:

  1. when the file is locked, a sleep that approximates estimated write duration and subsequent lock check allow for waiting. This could even be put in a while loop with an interval.
  2. fclose() does indeed not remove the lock from the process that's already running as confirmed in some of the answers.

PHP5.5 and lower on windows does not support the $wouldblock parameter according to the docs,
I was able to test this on Windows + PHP5.3 and concluded that the file_is_locked() from my test still worked in this scenario:
flock() would still return false just not have the $wouldblock parameter but it would still be caught in my else check.

if (isset($_POST['action'])) {
$file = 'file.txt';
$fp = fopen($file, 'r+');
if ($wouldblock = file_is_locked($fp)) {
// wait and then try again;
sleep(5);
$wouldblock = file_is_locked($fp);
}
switch ($_POST['action']) {
case 'write':
if ($wouldblock) {
echo 'already writing';
} else {
flock($fp, LOCK_EX);
fwrite($fp, 'yadayada');
sleep(5);
echo 'done writing';
}
break;
case 'read':
if ($wouldblock) {
echo 'cant read, already writing';
} else {
echo fread($fp, filesize($file));
}
break;
}

fclose($fp);
die();
}

function file_is_locked( $fp ) {
if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
if ($wouldblock) {
return 'locked'; // file is locked
} else {
return 'no idea'; // can't lock for whatever reason (for example being locked in Windows + PHP5.3)
}
} else {
return false;
}
}


Related Topics



Leave a reply



Submit