Recently a discussion came up on IRC that involved PHP's file locking mechanism.

File locking can be an interesting and somewhat confusing problem. If you've looked up flock() in the PHP manual you may have noticed that there quite a few comments that talk about the problems of file locking. Some of the solutions offered are creating separate lock files, lock directories, or even more complicated constructs of which many are unnecessary, inefficient, or just plain wrong. And while there are just as many helpful posts to be found, it's not always an easy task to determine which comment applies to your particular problem; Complexity is the price to pay for a powerful and versatile language such as PHP.

What is file locking?

Simply put, file locking allows you to solve the problem of concurrency by limiting access to a file to a particular program (read: process, thread, etc). Rather than several programs trying to access the same file at the same time and inadvertently overwriting each other's modifications, access is controlled, typically in a serialized manner.

For example, when developing a web page, you may encounter situations where you'd like to write data to a file , particularly if you've not yet become a victim of the Everything-goes-into-a-SQL-Database syndrome (/me raises hand). Perhaps you want to count how many users have visited your web page. A seemingly simple task: A small data file keeps track of the number of visitors. When someone visits your web page, you open the file, read the current number, increase it by 1, write the new number to the file, and close it again. Let's look at such a logic:

/*
Keeping track of visitors
*/// Filename that stores the counter
$filename = '/tmp/counter.txt';

// open the file
$fp = fopen($filename, 'a+');

// read in the old number of vistors
$visitors = fread($fp, filesize($filename));

// increase the number by one
$visitors++;

// erase the file contents
ftruncate($fp,0);

// write the new number of visitors
fwrite($fp, $visitors);

// close the file
fclose($fp);

Looks solid. And as long as only one user accesses your web site at a time, it should work as intended. Most web pages, however, receive more visitors than that. Let's step through the code and see what happens when two visitors view your web page at the same time. For this example we assume you've already had 98 visitors:

Visitor A: Clicks on a link to your web page.
Visitor B: Clicks on a link to your web page.
Visitor A: the counter code runs, the counter.txt file is opened and the value 98 is read.
Visitor B: the counter code runs, the counter.txt file is opened and the value 98 is read.
Visitor A: the $counter++ line increases the number to 99.
Visitor B: the $counter++ line increases the number to 99.
Visitor A: the new value of 99 is written to the counter.txt.
Visitor B: the new value of 99 is written to the counter.txt.

Whoops, what happened? You started with a value of 98, had two visitors, and now you're at 99? Perhaps this isn't a life threatening situation, but you wouldn't want your bank to operate the same way.

And this is where file locking comes in handy.

$filename = '/tmp/locktest.txt';echo("Going to open file $filename ... ");
if ($fp = fopen($filename, 'a+'))
{
echo("OK");
}
else
{
echo("failed.");
}
flush();

echo("Attempting to LOCK $filename ... ");
if (flock($fp, LOCK_EX))
{
echo("OK");
}
else
{
echo("failed.");
}
flush();

echo("Attempting to read from $filename ... ");
$contents = fread($fp, filesize($filename));
echo("Content = '$contents'");
flush();

sleep($_GET['sleep']);

$no = 'The magic number is: ' . rand(1,100);
echo("Writing '$no' into $filename ... ");
ftruncate($fp,0);
fwrite($fp, $no);
flock($fp, LOCK_UN);
fclose($fp);
echo("All done.");

http://localhost/locktest.php?sleep=5

Going to open file /tmp/locktest.txt … OK
Attempting to LOCK /tmp/locktest.txt … OK
Attempting to read from /tmp/locktest.txt … Content = 'The magic number is: 22'
Writing 'The magic number is: 57' into /tmp/locktest.txt … All done.

http://localhost/locktest.php?sleep=1

Going to open file /tmp/locktest.txt … OK
Attempting to LOCK /tmp/locktest.txt … OK
Attempting to read from /tmp/locktest.txt … Content = 'The magic number is: 57'
Writing 'The magic number is: 40' into /tmp/locktest.txt … All done.

This post has no comment. Add your own.

Post a comment