Oleg
Oleg

Reputation: 23277

Ensure that there is only one running PHP process on Linux

I have a php script running each minute from cron. but sometimes it works longer than 1 minute. My question is: what is the best way to ensure that only one process is running right now?

I use this code:

$output = shell_exec('ps aux | grep some_script.php | grep -v grep');   //get all processes containing "some_script.php" and exclude current grep process
$trimmed = rtrim($output, PHP_EOL); //trim newline symbol in the end
$processes = explode(PHP_EOL, $trimmed);    //get the array of lines (i.e. processes)
$procCnt = count($processes);   //get number of lines
if ($procCnt > 2) {
  echo "busy\n";
  exit();       //exit if number of processes more than 2 (see explaination below)
}

If there is one some_script process running from cron, shell_exec returns something like that:

apache     13593  0.0  0.0   9228  1068 ?        Ss   18:20   0:00 /bin/bash -c php -f /srv/www/robot/some_script.php 2>&1
apache     13602  0.0  0.0 290640 10544 ?        S    18:20   0:00 php -f /srv/www/robot/some_script.php

so if I have more than 2 lines in output, I call exit()

I want to ask: am I on a right way? Or is there a better way?

Any help would be appreciated

Upvotes: 3

Views: 1755

Answers (3)

John
John

Reputation: 3535

Just for OOP style, I am using separated Helper class.

flock — Portable advisory file locking

register_shutdown_function — Register a function for execution on shutdown

<?php

class ProcessHelper
{
    const PIDFILE = 'yourProcessName.pid';

    public static function isLocked()
    {
        $fp = fopen(self::PIDFILE, 'w');

        if (!flock($fp, LOCK_EX | LOCK_NB, $wouldBlock)) {
            if ($wouldBlock) {
                // if this file locked by other process
                var_dump('DO NOTHING');
                return true;
            }
        } else {
            var_dump('Do something and remove PID file');
            register_shutdown_function(function () {
                unlink(self::PIDFILE);
            });
            sleep(5); //for test, uncomment
        }
        return false;
    }
}


class FooService
{
    public function init()
    {
        if (!ProcessHelper::isLocked()) {
            var_dump('count 1000\n');
            for ($i = 0; $i < 10000; $i++) {
                echo $i;
            }
        }
    }
}

$foo = new FooService();
$foo->init();

Upvotes: 0

Izkata
Izkata

Reputation: 9323

Checking that way creates a race condition where two processes can retrieve the list at the same time, then both decide to exit. Depending on what you're trying to do, this may or may not be a problem.

A possible better alternative is to create a lock of some sort. A simple one I've used is a directory that only exists while the process is running - mkdir is atomic, it will either succeed (no other process is running) or fail (another process has already created it). Just make sure to remove it when complete:

if (!mkdir("lock_dir")) {
   echo "busy\n";
   exit();
}
register_shutdown_function(function() {
   rmdir("lock_dir");
});

Or better, it looks like flock was made for a similar purpose. This is the example from the manual:

<?php

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

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    ftruncate($fp, 0);      // truncate file
    fwrite($fp, "Write something here\n");
    fflush($fp);            // flush output before releasing the lock
    flock($fp, LOCK_UN);    // release the lock
} else {
    echo "Couldn't get the lock!";
}

fclose($fp);

?>

Just hold the lock for the script's runtime, similar to my first example.

Upvotes: 5

StormRideR
StormRideR

Reputation: 1758

Simplest method is to create file while process start, and delete it at very end. And check if file exist, then proceed and create it or die.

Another one is limit MaxClients for apache to one (if it is valid option in your case).

Upvotes: 1

Related Questions