LOCK FILE PHP for read then cannot be read? - php

I am locking files but then when I read them I cannot, my code...
// ACQUIRE READ LOCK
if(flock($file, LOCK_SH)) {
// READ HASHES FILE
if($contents = file('haasdas.txt')) {
// RELEASE READ LOCK
flock($file, LOCK_UN);
} else {
echo 'errrrrrorzzzer';
}
}
What is going on here?

As the docs mention, flock() works not on a filename, but on a file descriptor:
$fd=fopen($filename,'rb');
while (!flock($fd,LOCK_SH)) usleep(500);
$fs=fstat($fd);
$contents=fread($fd,$fs['size']);
flock($fd,LOCK_UN);
fclose($fd);
error handling is left as an exercise to the reader ...

Related

How to make improve this simple counter on php

I make this simple counter
$now = date ("d");
$filename = $now .".txt";
$lastcount="";
if (file_exists($filename))
{
if (time()-filemtime($filename) > 2 * 86400) {
} else {
$lastcount=strval(intval (file_get_contents($filename))+1);
}
}
file_put_contents($filename, $lastcount);
Basically it reads a file, then add 1, then rewrite
The problem is between the time I read the file, and writing it back, another copy of the program may read the file and write it.
So how do I make that atomic?
I also want to ensure that the whole script won't "crash" because of this locking.
So how to improve this counter?
You can lock file using flock. Use exclusive locking to write to file safely:
$fp = fopen($filename, "rw");
if (flock($fp, LOCK_EX)) {
// write here
// ...
// release the file
flock($fp, LOCK_UN);
} else {
// can't use it yet. Wait a little.
}

flock() keeps waiting for lock to be released

so i want to lock a file so i can see that a php process is already running. The example code looks as follows:
<?php
$file = fopen("test.txt","w+");
if (flock($file,LOCK_EX))
{
fwrite($file,"Write something");
sleep(10);
flock($file,LOCK_UN);
}
else
{
echo "Error locking file!";
}
fclose($file);
?>
But the problem is that when i executed this file, and execute the file again the second waits for the first one to be done with the lock. So then both are successfully executed. But only the first one has to be executed successfully. Anyknow know how to do this?
It sounds like you don't want flock to be blocking? You just want the first process to obtain the lock and the second one to fail?
To do that, you can use the LOCK_NB flag to stop the flock call from blocking:
$file = fopen("test.txt","r+");
if (flock($file,LOCK_EX | LOCK_NB))
{
fwrite($file,"Write something");
sleep(10);
flock($file,LOCK_UN);
}
else
{
echo "Error locking file!";
}
fclose($file);
More info is available on the PHP flock doc page - http://php.net/manual/en/function.flock.php
You could use a second file called e.g. "test.txt.updated" and maintain the state there -- i.e., whether "test.txt" was already updated or not. Something like the following. NOTE, I've opened "text.txt" in append-mode to see whether two concurrent runs really wrote only once to the file.
<?php
if (! ($f = fopen("text.txt", "a")) )
print("Cannot write text.txt\n");
elseif (! flock($f, LOCK_EX))
print("Error locking text.txt\n");
else {
print("Locked text.txt\n");
if (file_exists("text.txt.updated"))
print("text.txt already updated\n");
else {
print("Updating text.txt and creating text.txt.updated\n");
fwrite($f, "Write something\n");
if ($stamp = fopen("text.txt.updated", "w"))
fwrite($stamp, "whatever\n");
else
print("Oooooops, can't create the updated state file\n");
sleep(10);
}
flock($f, LOCK_UN);
}
?>

Is it safe to use file_get_contents on a flock-ed file?

From my old-school developper POV, doing this is strange:
$f = fopen($this->_fileName, "r");
if ($f===false) {
throw new Exception("bla");
}
$locked = flock($f, LOCK_SH);
if (!$locked) {
throw new Exception("bla");
}
$content = file_get_contents($this->_fileName);
flock($f, LOCK_UN);
fclose($f);
Or doing something like:
$f = fopen($this->_fileName, "r");
if ($f===false) {
throw new Exception("bla");
}
$locked = flock($f, LOCK_SH);
if (!$locked) {
throw new Exception("bla");
}
$content = fread($f, filesize($this->_fileName));
flock($f, LOCK_UN);
fclose($f);
i.e. if I'm opening with fopen(), I should use $f all the time. But in my two samples, two functions dont use the $f and I'm sure they try to open the file to read some information in it: file_get_contents() and filesize().
I could do a loop:
$content = '';
while(!feof($f))
$content .= fread($f, 4096);
flock($f, LOCK_UN);
fclose($f);
But I remember that I've read some time ago that this loop is much slower than the two solutions I'm suggesting. What is the safest and the most effective way of doing this (safest comes first)?
Yes. All code accessing the shared resource (in this case $this->fileName) simply needs to agree to lock something, not necessarily the shared resource.
For example (pseudo-code):
$lock = flock('/tmp/lockfile');
file_put_contents('/etc/foo.ini', 'whatever');
funlock($lock);
Notice the thing I'm locking is not the same thing I'm overwriting.
However, traditionally flock is applied to the file handle for the shared file, simply because it's easier than opening another handle just for a lock file (and saves the memory allocation of said opening). There's no requirement for that, though, just programmer ease.
In general, flock is a means to provide a cooperative, advisory, read-write locking mechanism on top of the file system: it doesn't actually do anything to the thing being locked.

Test if file is locked

In PHP, how can I test if a file has already been locked with flock? For example, if another running script has called the following:
$fp = fopen('thefile.txt', 'w');
flock($fp, LOCK_EX);
if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
if ($wouldblock) {
// another process holds the lock
}
else {
// couldn't lock for another reason, e.g. no such file
}
}
else {
// lock obtained
}
As described in the docs, use LOCK_NB to make a non-blocking attempt to obtain the lock, and on failure check the $wouldblock argument to see if something else holds the lock.
if(stream_get_meta_data($fp)['blocked'])
echo 'file is locked';
else
echo 'file is not locked';
Check it like this:
if (!flock($file, LOCK_EX)) {
throw new Exception(sprintf('File %s is locked', $file));
}
fwrite($file, $write_contents);

Lock files in PHP without using flock

When a user upload a file(users can upload multiple files)
exec('nohup php /main/apache2/work/upload/run.php &');
I am using nohup as the it needs to be executed in the back end.
In my original design run.php scans the directory using scandir everytime it's executed. Get an exclusive lock LOCK_EX on the file using flock and use LOCK_NB to skip the file if it has a lock and go the next one. If a file has a lock //Do logic. The problem is that the server is missing fcntl() library and since flock uses that library to execute the locking mechanism, flock won't work at the moment. It's going to take a month or two to get that installed(I have no control over that).
So my work around for that is have a temporary file lock.txt that acts a lock. If the filename exists in lock.txt skip the file and go to the next one.
$dir = "/main/apache2/work/upload/files/";
$files = scandir($dir);
$fileName = "lock.txt";
for($i=0; $i<count($files); $i++)
{
if(substr(strrchr($files[$i],'.csv'),-4) == '.csv')
{
if($file_handle = fopen("$fileName", "rb"))
{
while(!feof($file_handle))
{
$line = fgets($file_handle);
$line = rtrim($line);
if($line == "")
{
break;
}
else
{
if($files[$i] == $line)
{
echo "Reading from lock: ".$line."</br>";
$i++; //Go to next file
}
}
}
fclose($file_handle);
}
if($i >= count($files))
{
die("$i End of file");
}
if($file_handle = fopen("$fileName", "a+"))
{
if(is_writable($fileName))
{
$write = fputs($file_handle, "$files[$i]"."\n");
//Do logic
//Delete the file name - Stuck here
fclose($file_handle);
}
}
}
else
{
//Do nothing
}
}
How can I delete the filename from lock.txt?
More importantly, is there a better way to lock a file in php without using flock?
Having a shared lock database simply moves the locking problem to that file; it doesn't solve it.
A much better solution is to use one lock file per real file. If you want to lock access to myFile.csv then you check file_exists('myFile.csv.lock') and touch('myFile.csv.lock') if it doesn't exist. And unlink('myFile.csv.lock') when done.
Now, there is a possible race-condition between file_exists() and touch(), which can be mitigated by storing the PID in the file and checking if getmypid() is indeed the process holding the lock.

Categories