php: exclusive lock on file never obtained - php

On my php script, after parse a string, i need to write some data into a file (after create it if doesn't exist). Before write file, i need exclusive lock for avoid problems.
This is my code:
foreach ($elements[0] as $current) {
$file_handler = fopen($my_folder . "/" . $current . ".txt", "a");
$locked = flock($file_handler, "LOCK_EX");
while (!$locked) {
usleep(500000);
$locked = flock($file_handler, "LOCK_EX");
}
//got lock
fwrite($file_handler, $mystring . "\n");
//release lock
flock($file_handler, LOCK_UN);
fclose($file_handler);
}
return;
Now, something seems not work.
fopen create file, but my code seems go in loop inside while (file is created, but nothing was write inside). What's wrong?

You are passing the argument for locking as a string instead of a constant.
Try:
$locked = flock($file_handler, LOCK_EX);
Notice the missing double quotes around LOCK_EX

Related

How to create a PHP counter that may survive to concurrent visitors?

I developed a very simple counter in PHP. It works as expected but occasionally it resets to zero. No idea why. I suspect it could be related to concurrent visitors but I have no idea how to prevent that in case I am correct. Here is the code:
function updateCounter($logfile) {
$count = (int)file_get_contents($logfile);
$file = fopen($logfile, 'w');
if (flock($file, LOCK_EX)) {
$count++ ;
fwrite($file, $count);
flock($file, LOCK_UN);
}
fclose($file);
return number_format((float)$count, 0, ',', '.') ;
}
Thank you in advance.
file_get_contents on a locked file will probably get a "false" (== 0) and the logfile is probably unlocked again, when it comes to writing.
A classic race condition...
As file_get_contents() can return false accessing a previously locked file, the consequent fwrite() may write a zero or 1, resetting our counter to zero.
So we try to read the counter file after the locking has been succeeded for us.
function updateCounter($logfile) {
//$count = (int)file_get_contents($logfile);
if(file_exists($logfile)) {
$mode = 'r+';
} else {
$mode = 'w+';
}
//
$file = fopen($logfile, $mode);
//
if (flock($file, LOCK_EX)) {
//
// read counter file:
//
$count = (int) fgets($file);
$count++ ;
//
// point to the beginning of the file:
//
rewind($file);
fwrite($file, $count);
flock($file, LOCK_UN);
}
fclose($file);
return number_format((float)$count, 0, ',', '.') ;
}
//
$logfile = "counter.log";
echo updateCounter($logfile);
Please see usernotes on https://www.php.net/manual/en/function.flock.php .
I would append a character into the file and use strlen on the file contents to get the hits. Please note that your file will get big overtime but this can be easily solved with a cronjob that sums it up and cache it into another readonly file.
You can also use !is_writeable and check if its locked and if so you can miss the hit or wait with a while loop until its writable. Tricky but it works. It depends how valuable each hit will be and how much effort you would like to invest in this counter.

Shared access: How to fix "fread(): Length parameter must be greater than 0"?

When I run this function on multiple scripts one script generated warning:
fread(): Length parameter must be greater than 0
function test($n){
echo "<h4>$n at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
$fp = fopen("$n.txt", "r");
$s = fread($fp, filesize("$n.txt") );
fclose($fp);
$fp = fopen("$n.txt", "w");
$s = $_SERVER['HTTP_USER_AGENT'].' '.time();
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
fwrite($fp, $s);
// fflush($fp);// flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
}
}
I try to write reading of the file for multiple users, but only one user can write the file. I know that when I use fwrite with flock - LOC_EX, next scripts must wait till the write is finished. But here it seems like filesize doesn't wait till the write operation is finished. My opinion is that it tries to reach the file when the file size is 0, and as a result this produces the problem: 0 bytes will be read from the file, when it is written by original script.
Is it possible to fix this for fread function?
Purpose of this script is to test fread with some limit and to check the data which I read later, if the data are really written when I did not used fflush.
function test($n){
echo "<h4>$n at ".time()."</h4>";
for ($i = 0; $i<50; $i++ ){
$start = microtime(true);
$fp = fopen("$n.txt", "r");
if(filesize($n.txt) > 0)
{
$s = fread($fp, filesize($n.txt) );
fclose($fp);
$fp = fopen("$n.txt", "w");
$s = $_SERVER['HTTP_USER_AGENT'].' '.time();
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
fwrite($fp, $s);
// fflush($fp);// flush output before releasing the lock
flock($fp, LOCK_UN); // release the lock
} else {
echo "Couldn't get the lock!";
}
}
else
{
echo "Filesize must be greater than 0";
}
}
}
please change $s variables name its use same things two time
$fp = fopen("$n.txt", "r");
$s = fread($fp, filesize("$n.txt") );
fclose($fp);
The error occurs in the middle line of the above three lines.
Firstly, these three lines could be rewritten into a single line as follows:
$s = file_get_contents("$n.txt");
However, these isn't necessary, as these three lines are entirely redundant in your code. They don't do anything useful.
What they do is open a file, store its contents to $s and then close it.
But you are then immediately setting $s to a different value, thus throwing away the previous value, and making it pointless to have read it from the file in the first place.
If you need to keep the original contents of the file, then use file_get_contents() and make sure you don't overwrite the contents of the variable.
If you don't need the original contents of the file, then just delete those three lines from your code.
Incidentally, this error highlights a couple of good coding practices that you should take on board: Firstly, never re-use a variable for two different things, and secondly always give your variables (and functions) good names. $s is not a good name; $previousFileContents would be a better name; it would have made the error much more obvious.

PHP Write / Update Textfile, don't works if randomly Multiple Threads are try to update Textfile

Here is my Code with filename
it does work without problems if lets say i just use
update.php?pokemon=pikachu
it updates pikachu value in my found.txt +0.0001
But now my problem, when i have multiple threads running and randomly
2 threads are
update.php?pokemon=pikachu
and
update.php?pokemon=zaptos
i see the found.txt file
is empty than!!
so nothing is written in it then anymore.
So i guess its a bug when the php file is opened and another request is posted to the server.
How can i solve this problem this does accour often
found.txt
pikachu:2.2122
arktos:0
zaptos:0
lavados:9.2814
blabla:0
update.php
<?php
$file = "found.txt";
$fh = fopen($file,'r+');
$gotPokemon = $_GET['pokemon'];
$users = '';
while(!feof($fh)) {
$user = explode(':',fgets($fh));
$pokename = trim($user[0]);
$infound = trim($user[1]);
// check for empty indexes
if (!empty($pokename)) {
if ($pokename == $gotPokemon) {
if ($gotPokemon == "Pikachu"){
$infound+=0.0001;
}
if ($gotPokemon == "Arktos"){
$infound+=0.0001;
}
if ($gotPokemon == "Zaptos"){
$infound+=0.0001;
}
if ($gotPokemon == "Lavados"){
$infound+=0.0001;
}
}
$users .= $pokename . ':' . $infound;
$users .= "\r\n";
}
}
file_put_contents('found.txt', $users);
fclose($fh);
?>
I would create an exclusive lock after open the file and then release the lock before closing the file:
For creating an exclusive lock over the file:
flock($fh, LOCK_EX);
To delete it:
flock($fh, LOCK_UN);
Anyway you will need to check if other threads hot already the lock, so the first idea coming up is to try a few attempts to get the lock and if it's not finally possible, to inform the user, throw an exception or whatever other action to avoid an infinite loop:
$fh = fopen("found.txt", "w+");
$attempts = 0;
do {
$attempts++;
if ($attempts > 5) {
// throw exception or return response with http status code = 500
}
if ($attempts != 1) {
sleep(1);
}
} while (!flock($fh, LOCK_EX));
// rest of your code
file_put_contents('found.txt', $users);
flock($fh, LOCK_UN); // release the lock
fclose($fh);
Update
Probably the issue still remains because the reading part, so let's create also a shared lock before start reading and also let's simplify the code:
$file = "found.txt";
$fh = fopen($file,'r+');
$gotPokemon = $_GET['pokemon'];
$users = '';
$wouldblock = true;
// we add a shared lock for reading
$locked = flock($fh, LOCK_SH, $wouldblock); // it will wait if locked ($wouldblock = true)
while(!feof($fh)) {
// your code inside while loop
}
// we add an exclusive lock for writing
flock($fh, LOCK_EX, $wouldblock);
file_put_contents('found.txt', $users);
flock($fh, LOCK_UN); // release the locks
fclose($fh);
Let's see if it works

Why file_put_contents() not always rewrites a file?

I wonder, why PHP file_put_contents() function works in a weird way.
I used it in a loop to write some logs to file and all was fine (new lines were appended even if no flag was specified). When I started the script again, it re-created my file.
From PHP doc:
If filename does not exist, the file is created. Otherwise, the
existing file is overwritten, unless the FILE_APPEND flag is set.
OK, so my question is: Why (when used in one loop) it doesn't overwrite my file (without FILE_APPEND flag of course)? Bug or feature? :)
Edit: Example context of use when this happened:
$logFile = dirname ( __FILE__ ) . '/example.log';
foreach($something1 as $sth1) {
$logData .= "Something\n";
foreach($something2 as $sth2) {
if($something_else) {
$logData .= "Line: \t" . $sth2 . "\n";
file_put_contents($logFile, $logData);
}
}
}
As it has been very clearly mentioned in this link under the flags content(which you should have read) it clearly states that if file filename already exists, append the data to the file instead of overwriting it(when this flag is set). So when the flag for FILE_APPEND is set it appends and when not it rewrites. Hope this helped you.
Alternative Way
<?php
$file = 'file.txt';
$append = true;
if (file_exists($file)) {
if ($append) {
// append file
$file = fopen($file, 'a+');
} else {
// overwrite file
$file = fopen($file, 'a');
}
} else {
// create file
$file = fopen($file, 'a');
}
fwrite($file, 'text');
fclose($file);
?>
here is a php fopen documentation
and php file
and read on its related topics
ok, when you are run the script each time try to rename the log file with random number or currentdate timestamp and try to save it in your DB
by this when you again run the script and can take the log file name from DB and update it when you needed

Load file lines into an array AFTER the file is open and locked (PHP)

Is there a quick way to load every line of a file into an array from a file once it has already been opened?
For example:
$handle = fopen("file", "r+");
flock($handle, LOCK_EX);
$array = load_lines($handle); <- need this
// compute on the array
fwrite($handle, $array);
flock($handle, LOCK_UN):
fclose($handle);
The reason I need this is because I currently use the file() function to grab the contents of a file and put them into an array. However, I need to incorporate file locking into my design and I'm hoping to not have to change it too much (it is current array-based). Is there an easy way to do this?
On php <5.3, or if you choose to with LOCK_NB, file locks in php are advisory. That is, you have to test the lock yourself .. they don't actually prevent you from updating the file. This will do:
$fh = fopen(__FILE__, 'r+');
if (flock($fh, LOCK_EX)) {
$array = file(__FILE__);
fwrite($fh, implode($array));
flock($fh, LOCK_UN);
flcose($fh);
}
else {
echo "Could not acquire the lock!"
}
I also tested this out in php 5.3. It seems that file() ignores locking.
Try this:
function load_lines($handle)
{
$array = array();
while(!feof($handle)
{
$array[] = fgets($handle);
}
return $array;
}

Categories