Why are "lock" files used in PHP instead of just counting the processes?

Viewed 1104

I've seen a lot of examples where a "lock" file is used to keep track of if a PHP script is currently running.

Example:

  1. script starts
  2. checks if "/tmp/lockfile" is currently locked
  3. If it is locked, exit. If not, lock the file and continue

This way, if a long-running script is started up twice, only the first instance will run. Which is great.

However, it seems like the wrong way to go around it. Why don't we just check if the process is already running like this?

if(exec("ps -C " . basename(__FILE__) . " --no-headers | wc -l") > 1){
  echo "Already running.";
  exit;
}

Are there any potential pitfalls to this method? Why do I see the "lock" file workaround so often? It definitely seems more accurate to count the processes with the name we're looking for....

4 Answers

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.

The most important reasons that lock files are used, unfortunately not given in the other answers, is that lock files use a locking mechanism that is atomic, allow you to run multiple instances your script, at a higher level context than the script itself, and are more secure.

Lock files are atomic

Iterating through a process list is inherently prone to race conditions; in the time it takes to retrieve and iterate through the list a second process might have just spawned, and you unintentionally end up with multiple processes.

The file locking mechanism is strictly atomic. Only one process can get an exclusive lock to a file, so when it does have a lock, there is no possibility the same command will be running twice.

Lock files allow for multiple instances of the same script

Say you want to have several separate instances of a script running, but each with its own scope/options. If you'd simply count the number of times the script appears in ps output, you'd only be able to run a single instance. By using separate lock files you can properly lock its running state in an individual scope.

Lock files are more secure

Lock files are simply a much more elegant and secure design. Instead of abusing a process list (which requires a lot more permissions and would have to be accessed and parsed differently on each OS) to deduce if a script is already running, you have a single dedicated system to explicitly lock a state.

Summary

So to reiterate all the reasons lock files are used over other methods:

  • Lock files allow for atomic locking, negating race conditions.
  • They allow for running multiple instances/scopes of the same script.
  • File system access is more universally available than execute permissions or full access to process information.
  • They are more secure, because no permissions outside of the specific lock file are needed.
  • Using file locks is more compatible across different operating systems.
  • It is a more elegant solution; doesn't require parsing through a process list.

As for the cons mentioned in other answers; that the lock file can be deleted and sometimes the lock is transferred to another process:

Deletion of lock files is prevent by setting proper permissions, and storing the file on non-volatile storage. Lock files are "definite" if used according to spec, unlike a process list.

It's true a process that forks a child process wil "bequeath" it's lock to any child processes that are still running, however this is easily remedied by specifically unlocking the file once the script is done. E.g. using flock --unlock.

Long story short: you should always use lock files over parsing running processes.

Other solutions

There are other solutions, though they usually offer no benefit over simple file locks unless you have additional requirements:

  • Mutexes in a separate database / store (e.g. redis).
  • Exclusively listening to a port on a network interface.

First, the command is not right . When I run php test.php and the command

 ps -C test.php

whill get nothing. You can use ps -aux|grep 'test.php' -c to get process number.But exec("ps -aux|grep 'php test.php' -c"); return number must minus 2 is the real process number

The reason to use lock file most reason is exec or other command function need some special permission, and is disable_functions in php.ini default config.

Test script like this:

$count = exec("ps -aux|grep 'php test.php' -c");

if($count > 3){
  echo "Already running.".$count;
  exit;
}

while(1){
    sleep(20);
}

The main reason why "lock files" are used is simply because they can be expected to work in any host-environment. If you can "create a file," and if you can "lock it," such code will work ... anywhere.

Also – "there is nothing to be gained by being 'clever.'" This well-known strategy is known to work well – therefore, "run with it."

Related