Limitting concurrent runs in PHP


Most PHP tasks are designed to be run concurrently, that is, several instances of the same task can run simultaneously. For example, my home page should be displayed to all viewers, even if they’re trying to access it at the same time.

Other tasks, however, should only be run one at a time. For instance, an update (cron) task should probably refuse to run (or wait) if it sees another task running. Here’s how to implement that.

I have a web based RSS reader installed on my site here. To update all of my feeds (and Chaya Tova’s) takes it between 3 and 6 minutes. I don’t want it to start if the previous one is running, however, setting the cron job to run it every 10 minutes is not a suitable solution for two reasons:

  1. What if a run takes 11 minutes?
  2. If a run finishes in 2 minutes, why should I wait 8 more minutes*?

* Note that caching is implemented, but not on this layer. There’s also a field that limits how frequently a given feed should be polled… the default is 60 minutes, but some are hit as often as every two minutes!

In order to do this, I make use of a lockfile. A lockfile is a special file that’s created to indicate that a process is running, and deleted (or closed) when it’s done.

This is done as follows:

$lock_filename = /path/to/lockfile;

$fp = fopen($lock_filename, "w");

if (!flock($fp, LOCK_EX + LOCK_NB )) { 
   $lastlock = fileatime($lock_filename);
   $current = time();
   $delta = $current - $lastlock;
   if ($delta > 300) // Only if more than 5m, to suppress spurious mails
   {
       print "Prior session still running -- started $delta seconds ago\n";
   }
   exit;
}

Here, we attempt to open the file for write access. We then request an exclusive lock (meaning no one else can also for any type of lock), and tell flock not to block (wait) if it can’t acquire the lock. If that fails, it means the file is already locked. We check the last access time (fileatime) to see when it was locked, and print a message if it’s too long (so I can find processes that are really slow).

At the end of the PHP file, I unlock the file and delete the lock file:

flock($fp, LOCK_UN); // release the lock
fclose($fp);
unlink($lock_filename);

Enjoy!


Leave a Reply

Your email address will not be published. Required fields are marked *