Запуск php-скрипта через ajax, но только если он еще не запущен

Я намерен это сделать.

My client.html вызывает php script check.php через ajax. Я хочу check.php, чтобы проверить, запущен ли еще один скрипт 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); } 

Я думаю, что это все, что мне нужно, но, как вы можете видеть, я не уверен, как сделать некоторые из них.

Я использовал бы механизм 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's 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's 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 запускается один раз. Это связано с тем, что состояние гонки я упомянул в комментариях ниже этого ответа.

Вы можете это реализовать, используя файл блокировки:

 if(is_file(__DIR__.'/work.lock')) { die('Script already run.'); } else { file_put_contents(__DIR__.'/work.lock', ''); // YOUR CODE unlink(__DIR__.'/work.lock'); } 

Жаль, что я не видел этого, прежде чем он был принят.

Я написал класс, чтобы сделать именно это. (с использованием блокировки файлов) и PID, проверка идентификатора процесса, как в Windows, так и в Linux.

https://github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php

Я думаю, что вы действительно переусердствовали со всеми процессами и проверками фона. Если вы запускаете скрипт PHP 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 и показать статус пользователю.

Позволяет сделать весь процесс от B до D простым

Шаг BD:

 $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 

NB:

  1. Также рекомендуется поставить ту же проверку в файле task.php.

  2. Если task.php выполняется непосредственно через httpd (webserver), он будет отображаться только как httpd-процесс и не может быть идентифицирован командой «ps»

  3. Он не будет работать в условиях балансировки нагрузки. [Отредактировано: 17Jul17]

Вы можете получить исключительную блокировку самого скрипта в течение всего запуска скрипта

Любые другие попытки запустить его закончатся, как только будет вызвана функция 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; } } } 

Контрольная работа

Запустите это в одной оболочке и попробуйте запустить ее в другой, пока она ждет ввода.

 lock(); //this will pause execution until an you press enter echo '...continue? [enter]'; $handle = fopen("php://stdin","r"); $line = fgets($handle); fclose($handle);