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?)

3
  • 3
    Is the script running under a webserver, or command line? Commented Jan 31, 2012 at 16:58
  • If it really matters... web server. Commented Jan 31, 2012 at 17:03
  • 1
    probably a mysql lock, in that case, so the script can gracefully abort and say "alread in use". using flock and the like would lock the script off from apache itself and probably cause 500 internal errors and whatnot. Commented Jan 31, 2012 at 17:06

6 Answers 6

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?

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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;
}

Comments

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 https://www.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.

1 Comment

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.
0

Perhaps this could work for you,

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

Comments

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
**/
?>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.