Reputation: 23277
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
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
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
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