Запуск 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);