Запуск PHP скрипт через ajax, но только если он еще не запущен
Мое намерение таково.
My client.html вызывает php script check.php через ajax. Я хочу check.php, чтобы проверить, запущен ли еще script task.php. Если да, я ничего не делаю. Если это не так, мне нужно запустить его в фоновом режиме.
У меня есть идея, что я хочу сделать, но я не уверен, как это сделать.
Часть A. Я знаю, как вызвать check.php через ajax.
Часть B. В check.php мне может потребоваться запустить task.php. Мне кажется, мне нужно что-то вроде:
$PID = shell_exec("php task.php > /dev/null & echo $!");
Я думаю, что " > /dev/null &" бит говорит, что он работает в фоновом режиме, но я не уверен, что такое "$!" делает.
Часть C. $PID Мне нужно как тэг процесса. Мне нужно записать этот номер (или что-то еще) в файл в том же каталоге и прочитать его каждый раз на check.php. Я не могу понять, как это сделать. Может ли кто-нибудь дать мне ссылку о том, как читать/записывать файл с одним номером в тот же каталог?
Часть D. Затем, чтобы проверить, все ли запущен последний запущенный task.php, я буду использовать функцию:
function is_process_running($PID)
{
exec("ps $PID", $ProcessState);
return(count($ProcessState) >= 2);
}
Я думаю, что это все необходимые мне биты, но, как вы можете видеть, я не уверен, как сделать некоторые из них.
Ответы
Ответ 1
Я бы использовал механизм flock()
, чтобы убедиться, что task.php
работает только один раз.
Используйте такой код:
<?php
$fd = fopen('lock.file', 'w+');
// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
// run your code
sleep(10);
// ...
flock($fd, LOCK_UN);
} else {
echo 'already running';
}
fclose($fd);
Также обратите внимание, что flock()
, как указывает документация PHP, переносится во всех поддерживаемых операционных системах.
!$
выдает pid последней выполненной программы в bash. Вот так:
command &
pid=$!
echo pid
Обратите внимание, что вам нужно убедиться, что ваш PHP-код работает в системе с поддержкой bash. (Не окна)
Обновление (после комментария открывателя).
flock()
будет работать во всех операционных системах (как я уже упоминал). Проблема, которую я вижу в вашем коде при работе с окнами, - это !$
(как я уже упоминал;)..
Чтобы получить pid task.php, вы должны использовать proc_open()
, чтобы запустить task.php. Я подготовил два примера скриптов:
task.php
$fd = fopen('lock.file', 'w+');
// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
// your task code comes here
sleep(10);
// ...
flock($fd, LOCK_UN);
echo 'success';
$exitcode = 0;
} else {
echo 'already running';
// return 2 to let check.php know about that
// task.php is already running
$exitcode = 2;
}
fclose($fd);
exit($exitcode);
check.php
$cmd = 'php task.php';
$descriptorspec = array(
0 => array('pipe', 'r'), // STDIN
1 => array('pipe', 'w'), // STDOUT
2 => array('pipe', 'w') // STDERR
);
$pipes = array(); // will be set by proc_open()
// start task.php
$process = proc_open($cmd, $descriptorspec, $pipes);
if(!is_resource($process)) {
die('failed to start task.php');
}
// get output (stdout and stderr)
$output = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);
do {
// get the pid of the child process and it exit code
$status = proc_get_status($process);
} while($status['running'] !== FALSE);
// close the process
proc_close($process);
// get pid and exitcode
$pid = $status['pid'];
$exitcode = $status['exitcode'];
// handle exit code
switch($exitcode) {
case 0:
echo 'Task.php has been executed with PID: ' . $pid
. '. The output was: ' . $output;
break;
case 1:
echo 'Task.php has been executed with errors: ' . $output;
break;
case 2:
echo 'Cannot execute task.php. Another instance is running';
break;
default:
echo 'Unknown error: ' . $stdout;
}
Вы спросили меня, почему мое решение flock() - лучшее. Это просто потому, что другой ответ не будет надежно удостовериться, что task.php
работает один раз. Это связано с тем, что состояние гонки я упомянул в комментариях ниже этого ответа.
Ответ 2
Вы можете реализовать это, используя файл блокировки:
if(is_file(__DIR__.'/work.lock'))
{
die('Script already run.');
}
else
{
file_put_contents(__DIR__.'/work.lock', '');
// YOUR CODE
unlink(__DIR__.'/work.lock');
}
Ответ 3
Жаль, что я не видел этого, прежде чем он был принят.
Я написал класс, чтобы сделать именно это. (с использованием блокировки файлов) и PID, проверка идентификатора процесса на обоих окнах и Linux.
https://github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php
Ответ 4
Я думаю, что вы действительно переусердствовали со всеми процессами и проверками фона. Если вы запустите PHP
script without a session
, вы уже выполняете его в фоновом режиме. Потому что он не будет блокировать какой-либо другой запрос от пользователя. Поэтому убедитесь, что вы не вызываете session_start();
Тогда следующим шагом было бы запустить его, даже если пользователь отменит запрос, который является базовой функцией в PHP
. ignore_user_abort
Последняя проверка заключается в том, чтобы убедиться, что она запускается только один раз, что легко сделать при создании файла, поскольку PHP
не имеет простой области приложения.
Комбинированный:
<?php
// Ignore user aborts and allow the script
// to run forever
ignore_user_abort(true);
set_time_limit(0);
$checkfile = "./runningtask.tmp";
//LOCK_EX basicaly uses flock() to prevents racecondition in regards to a regular file open.
if(file_put_contents($checkfile, "running", LOCK_EX)===false) {
exit();
}
function Cleanup() {
global $checkfile;
unlink($checkfile);
}
/*
actual code for task.php
*/
//run cleanup when your done, make sure you also call it if you exit the code anywhere else
Cleanup();
?>
В своем javascript вы можете напрямую вызвать task.php и отменить запрос, когда установлено соединение с сервером.
<script>
function Request(url){
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
} else{
return false;
}
httpRequest.onreadystatechange = function(){
if (httpRequest.readyState == 1) {
//task started, exit
httpRequest.abort();
}
};
httpRequest.open('GET', url, true);
httpRequest.send(null);
}
//call Request("task.php"); whenever you want.
</script>
Бонусные баллы: у вас может быть фактический код для task.php для записи периодических обновлений $checkfile
, чтобы иметь представление о том, что происходит. Затем вы можете загрузить другой файл ajax и показать статус пользователю.
Ответ 5
Позволяет сделать весь процесс от B до D простым
Шаг B-D:
$rslt =array(); // output from first exec
$output = array(); // output of task.php execution
//Check if any process by the name 'task.php' is running
exec("ps -auxf | grep 'task.php' | grep -v 'grep'",$rslt);
if(count($rslt)==0) // if none,
exec('php task.php',$output); // run the task,
Пояснение:
ps -auxf --> gets all running processes with details
grep 'task.php' --> filter the process by 'task.php' keyword
grep -v 'grep' --> filters the grep process out
Примечание:
-
Также рекомендуется поставить ту же проверку в файл task.php.
-
Если task.php выполняется непосредственно через httpd (webserver), он будет отображаться только как httpd-процесс и не может быть идентифицирован командой "ps"
-
Это не будет работать в условиях балансировки нагрузки. [Отредактировано: 17Jul17]
Ответ 6
Вы можете получить эксклюзивную блокировку самого script в течение script
Любые другие попытки запустить его завершатся, как только вызывается функция lock().
//try to set a global exclusive lock on the file invoking this function and die if not successful
function lock(){
$file = isset($_SERVER['SCRIPT_FILENAME'])?
realpath($_SERVER['SCRIPT_FILENAME']):
(isset($_SERVER['PHP_SELF'])?realpath($_SERVER['PHP_SELF']):false);
if($file && file_exists($file)){
//global handle stays alive for the duration if this script running
global $$file;
if(!isset($$file)){$$file = fopen($file,'r');}
if(!flock($$file, LOCK_EX|LOCK_NB)){
echo 'This script is already running.'."\n";
die;
}
}
}
Test
Запустите это в одной оболочке и попробуйте запустить ее в другой, пока она ждет ввода.
lock();
//this will pause execution until an you press enter
echo '...continue? [enter]';
$handle = fopen("php://stdin","r");
$line = fgets($handle);
fclose($handle);