Я намерен это сделать.
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:
Также рекомендуется поставить ту же проверку в файле task.php.
Если task.php выполняется непосредственно через httpd (webserver), он будет отображаться только как httpd-процесс и не может быть идентифицирован командой «ps»
Он не будет работать в условиях балансировки нагрузки. [Отредактировано: 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);