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

мое намерение таково.

мой клиент.html вызывает проверку скрипта php.php через ajax. Мне нужен чек.php, чтобы проверить, есть ли другая задача скрипта.php уже запущен. Если так, то я ничего не делаю. Если это не так, мне нужно запустить его в фоновом режиме.

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

часть A. Я знаю, как вызвать проверку.php через ajax.

часть B. В проверке.php мне может понадобиться запустить задачу.РНР. Думаю, мне что-то нужно. например:

$PID = shell_exec("php task.php > /dev/null & echo $!");

Я думаю, что бит "> /dev/null & " говорит ему работать в фоновом режиме, но я не уверен, что "$!" делает.

часть C. $PID мне нужен в качестве тега процесса. Мне нужно записать этот номер (или что-то еще) в файл в том же каталоге и прочитать его каждый вызов для проверки.РНР. Я не могу придумать, как это сделать. Может ли кто-нибудь дать мне ссылку на чтение/запись файла с одним номером в тот же каталог?

часть D. Затем проверить, если последняя запущенная задача.php все еще работает я собираюсь использовать функцию:

function is_process_running($PID)
{
   exec("ps $PID", $ProcessState);
   return(count($ProcessState) >= 2);
}

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

6 ответов


я бы использовал 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-код работает на система с поддержкой Баш. (Не windows)


обновление (после комментария открывалка).

flock() будет работать на всех операционных системах (как я упоминал). Проблема, которую я вижу в вашем коде при работе с windows, - это !$ (как я уже говорил ;) ..

для получения pid задачи.PHP вы должны использовать proc_open() для запуска задач.РНР. Я подготовил два примера скрипты:

задач.в 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);

проверка.в 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;
}

Вы спрашивали почему мое стадо() решение является лучшим. Это просто потому, что другой ответ не будет надежно убедиться, что task.php выполняется один раз. Это связано с тем, что состояние гонки я упомянул в комментариях ниже этого ответа.


вы можете реализовать это, используя lock file:

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 теперь вы можете вызвать задачу.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>

бонусные баллы: вы можете иметь фактический код для задачи.php пишет случайные обновления для $checkfile чтобы иметь представление о том, что происходит. Тогда вы можете иметь другой файл ajax считывает содержимое и показывает статус пользователю.


позволяет сделать весь процесс от 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

NB:

  1. его также рекомендуется поставить ту же задачу проверки.PHP-файл.

  2. Если задач.php выполняется непосредственно через httpd (веб-сервер), он будет отображаться только как процесс 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);