I have a cron script that executes a PHP script every 10 minutes. The script checks a queue and processes the data in the queue. Sometimes the queue has enough data to last ov
You can just use a lock file. PHP's flock() function provides a simple wrapper for Unix's flock function, which provides advisory locks on files.
If you don't explicitly release them, the OS will automatically release these locks for you when the process holding them terminates, even if it terminates abnormally.
You can also follow the loose Unix convention of making your lock file a 'PID file' - that is, upon obtaining a lock on the file, have your script write its PID to it. Even if you never read this from within your script, it will be convenient for you if your script ever hangs or goes crazy and you want to find its PID in order to manually kill it.
Here's a copy/paste-ready implementation:
#!/usr/bin/php
<?php
$lock_file = fopen('path/to/yourlock.pid', 'c');
$got_lock = flock($lock_file, LOCK_EX | LOCK_NB, $wouldblock);
if ($lock_file === false || (!$got_lock && !$wouldblock)) {
throw new Exception(
"Unexpected error opening or locking lock file. Perhaps you " .
"don't have permission to write to the lock file or its " .
"containing directory?"
);
}
else if (!$got_lock && $wouldblock) {
exit("Another instance is already running; terminating.\n");
}
// Lock acquired; let's write our PID to the lock file for the convenience
// of humans who may wish to terminate the script.
ftruncate($lock_file, 0);
fwrite($lock_file, getmypid() . "\n");
/*
The main body of your script goes here.
*/
echo "Hello, world!";
// All done; we blank the PID file and explicitly release the lock
// (although this should be unnecessary) before terminating.
ftruncate($lock_file, 0);
flock($lock_file, LOCK_UN);
Just set the path of your lock file to wherever you like and you're set.
You can use new Symfony 2.6 LockHandler. Source
$lock = new LockHandler('update:contents');
if (!$lock->lock()) {
echo 'The command is already running in another process.';
}
Assuming this is a linux server and you have cronjobs available
///Check for running script and run if non-exist///
#! /bin/bash
check=$(ps -fea | grep -v grep | grep script.php | wc -l)
date=$(date +%Y-%m%d" "%H:%M:%S)
if [ "$check" -lt 1 ]; then
echo "["$date"] Starting script" >> /path/to/script/log/
/sbin/script ///Call the script here - see below///
fi
script file
#/usr/bin/php /path/to/your/php/script.php
If you need it to be absolutely crash-proof, you should use semaphores, which are released automatically when php ends the specific request handling.
A simpler approach would be to create a DB record or a file at the beginning of the execution, and remove it at the end. You could always check the "age" of that record/file, and if it's older than say 3 times the normal script execution, suppose it crashed and remove it.
There's no "silver bullet", it just depends on your needs.
If you are running Linux, this should work at the top of your script:
$running = exec("ps aux|grep ". basename(__FILE__) ."|grep -v grep|wc -l");
if($running > 1) {
exit;
}
I know this is an old question, but there's an approach which hasn't been mentioned before that I think is worth considering.
One of the problems with a lockfile or database flag solution, as already mentioned, is that if the script fails for some reason other than normal completion it won't release the lock. And therefore the next instance won't start until the lock is either manually cleared or cleared by a clean-up function.
If, though, you are certain that the script should only ever be running once, then it's relatively easy to check from within the script whether it is already running when you start it. Here's some code:
function checkrun() {
exec("ps auxww",$ps);
$r = 0;
foreach ($ps as $p) {
if (strpos($p,basename(__FILE__))) {
$r++;
if ($r > 1) {
echo "too many instances, exiting\n";
exit();
}
}
}
}
Simply call this function at the start of the script, before you do anything else (such as open a database handler or process an import file), and if the same script is already running then it will appear twice in the process list - once for the previous instance, and once for this one. So, if it appears more than once, just exit.
A potential gotcha here: I'm assuming that you will never have two scripts with the same basename that may legitimately run simultaneously (eg, the same script running under two different users). If that is a possibility, then you'd need to extend the checking to something more sophisticated than a simple substring on the file's basename. But this works well enough if you have unique filenames for your scripts.