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:
- 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.
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
What Is the Maximum Size of an Array in PHP
Validating Us Phone Number with PHP/Regex
Invoke External Shell Script from PHP and Get Its Process Id
How to Save Webpage as a Image File Using PHP
How to Add Paths to the Apache Path Variable
Call to Undefined Method Maatwebsite\Excel\Excel::Load()
Is It a Good Idea to Use $_Server['Document_Root'] in Includes
Unknown Modifier '/' Error in PHP
Call to Undefined Method Pdo::Execute()
Which MySQL Datatype to Use for an Ip Address
Composer Install Error - Requires Ext_Curl When It's Actually Enabled
Twig for Loop for Arrays with Keys
How to Display "12 Minutes Ago" etc in a PHP Webpage
PHP File Upload Error Tmp_Name Is Empty
Retrieve Data from Db and Display It in Table in PHP .. See This Code Whats Wrong with It