10

I'm trying to write a PHP script that I want to ensure only has a single instance of it running at any given time. All of this talk about different ways of locking, and race conditions, and etc. etc. etc. is giving me the willies.

I'm confused as to whether lock files are the way to go, or semaphores, or using MySQL locks, or etc. etc. etc.

Can anyone tell me:

a) What is the correct way to implement this?

AND

b) Point me to a PHP implementation (or something easy to port to PHP?)

Keith Palmer Jr.
  • 27,666
  • 16
  • 68
  • 105

6 Answers6

4

One way is to use the php function flock with a dummy file, that will act as a watchdog.

On the beginning of our job, if the file raise a LOCK_EX flag, exit, or wait, can be done.

Php flock documentation: http://php.net/manual/en/function.flock.php

For this examples, a file called lock.txt must be created first.

Example 1, if another twin process is running, it will properly quit, without retrying, giving a state message.

It will throw the error state, if the file lock.txt isn't reachable.

<?php

$fp = fopen("lock.txt", "r+");

if (!flock($fp, LOCK_EX|LOCK_NB, $blocked)) {
    if ($blocked) {

        // another process holds the lock
        echo "Couldn't get the lock! Other script in run!\n"; 

    }
    else {
        // couldn't lock for another reason, e.g. no such file
        echo "Error! Nothing done.";
    }
}
else {
    // lock obtained
    ftruncate($fp, 0);  // truncate file

    // Your job here 
    echo "Job running!\n";
    
    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock

}

fclose($fp); // Empty memory

Example 2, FIFO (First in, first out): we wants the process to wait, for an execution after the queue, if any:

<?php

$fp = fopen("lock.txt", "r+");

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    ftruncate($fp, 0);      // truncate file

    // Your job here 
    echo "Job running!\n";

    // Leave a breathe
    sleep(3);

    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock
}

fclose($fp);

It is also doable with fopen into x mode, by creating and erasing a file when the script ends.

Create and open for writing only; place the file pointer at the beginning of the file. If the file already exists, the fopen() call will fail by returning FALSE

http://php.net/manual/en/function.fopen.php


However, into a Unix environment, for fine tuning, I found easier to list the PID's of every background scripts with getmypid() into a DB, or a separate JSON file.

When one task ends, the script is responsible to declare his state in this file (eq: success/failure/debug infos, etc), and then remove his PID. This allows from my view to create admins tools and daemons in a simpler way. And use posix_kill() to kill a PID from PHP if necessary.

Micro-Services are composed using Unix-like pipelines. Services can call services. https://en.wikipedia.org/wiki/Microservices


See also: Prevent PHP script using up all resources while it runs?

NVRM
  • 11,480
  • 1
  • 88
  • 87
2

Use semaphores:

$key = 156478953; //this should be unique for each script
$maxAcquire = 1;
$permissions =0666;
$autoRelease = 1; //releases semaphore when request is shut down (you dont have to worry about die(), exit() or return
$non_blocking = false; //if true, fails instantly if semaphore is not free

$semaphore = sem_get($key, $maxAcquire, $permissions, $autoRelease);
if (sem_acquire($semaphore, $non_blocking ))  //blocking (prevent simultaneous multiple executions)
{
    processLongCalculation();
}
sem_release($semaphore);

See:

https://www.php.net/manual/en/function.sem-get.php

https://www.php.net/manual/en/function.sem-acquire.php

https://www.php.net/manual/en/function.sem-release.php

Bojan Hrnkas
  • 1,587
  • 16
  • 22
1
// borrow from 2 anwsers on stackoverflow
function IsProcessRunning($pid) {
    return shell_exec("ps aux | grep " . $pid . " | wc -l") > 2;
}

function AmIRunning($process_file) {
    // Check I am running from the command line
    if (PHP_SAPI != 'cli') {
        error('Run me from the command line');
        exit;
    }

    // Check if I'm already running and kill myself off if I am
    $pid_running = false;
    $pid = 0;
    if (file_exists($process_file)) {
        $data = file($process_file);
        foreach ($data as $pid) {
            $pid = (int)$pid;
            if ($pid > 0 && IsProcessRunning($pid)) {
                $pid_running = $pid;
                break;
            }
        }
    }
    if ($pid_running && $pid_running != getmypid()) {
        if (file_exists($process_file)) {
            file_put_contents($process_file, $pid);
        }
        info('I am already running as pid ' . $pid . ' so stopping now');
        return true;
    } else {
        // Make sure file has just me in it
        file_put_contents($process_file, getmypid());
        info('Written pid with id '.getmypid());
        return false;
    }
}

/*
 * Make sure there is only one instance running at a time
 */
$lockdir = '/data/lock';
$script_name = basename(__FILE__, '.php');
// The file to store our process file
$process_file = $lockdir . DS . $script_name . '.pid';

$am_i_running = AmIRunning($process_file);
if ($am_i_running) {
    exit;
}
tpham
  • 194
  • 1
  • 5
0

Perhaps this could work for you,

http://www.electrictoolbox.com/check-php-script-already-running/

Warren.S
  • 152
  • 1
  • 5
0

In case you are using php on linux and I think the most practical way is:

<?php
 if(shell_exec('ps aux | grep '.__FILE__.' | wc  -l')>3){
    exit('already running...');
 }
?>

Another way to do it is with file flag and exit callback, the exit callback will ensures that the file flag will be reset to 0 in any case of php execution end also fatal errors.

<?php
function exitProcess(){
  if(file_get_contents('inprocess.txt')!='0'){
    file_put_contents('inprocess.txt','0');  
  }
}

if(file_get_contents('inprocess.txt')=='1'){
  exit();
}

file_put_contents('inprocess.txt','1');
register_shutdown_function('exitProcess');

/**
execute stuff
**/
?>
talsibony
  • 8,448
  • 6
  • 47
  • 46
0

You can go for the solution that fits best your project, the two simple ways to achieve that are file locking or database locking.

For implementations of file locking, check http://us2.php.net/flock

If you already use a database, create a table, generate known token for that script, put it there, and just remove it after the end of the script. To avoid problems on errors, you can use expiry times.

Guilherme Viebig
  • 6,901
  • 3
  • 28
  • 30
  • Are you sure this is a safe way to handle it? The comments on PHP.net have several entries indicating that it may introduce race conditions. – Keith Palmer Jr. Jan 31 '12 at 17:52