Тайм-аут функции PHP

Я не эксперт по PHP. У меня есть функция, которая использует EXEC для запуска WINRS, которая затем запускает команды на удаленных серверах. Проблема в том, что эта функция помещена в цикл, который вызывает функцию getservicestatus десятки раз. Иногда команда WINRS может застрять или занять больше времени, чем ожидалось, из-за чего PHP-скрипт отключился и выдал ошибку 500.

Временно я снизил значение установленного тайм-аута в PHP и создал пользовательскую страницу 500 в IIS, и если ссылающаяся страница совпадает с именем скрипта, перезагрузите страницу (иначе, выведите ошибку). Но это грязно. И очевидно, что это не относится к каждому вызову функции, поскольку она глобальная. Таким образом, он позволяет избежать остановки страницы только при ошибке HTTP 500.

Что я действительно хотел бы сделать, так это установить тайм-аут на 5 секунд для самой функции. Я искал довольно много и не смог найти ответ, даже на stackru. Да, есть похожие вопросы, но я не смог найти ни одного, связанного с моей функцией. Возможно, есть способ сделать это при выполнении команды, такой как альтернатива exec()? Я не знаю. В идеале я бы хотел, чтобы функция отключалась через 5 секунд и возвращала $servicestate как 0.

Код комментируется, чтобы объяснить мой беспорядок в спагетти. И мне жаль, что вы должны это увидеть...

function getservicestatus($servername, $servicename, $username, $password)
{
//define start so that if an invalid result is reached the function can be restarted using goto.
start:

//Define command to use to get service status.
$command = 'winrs /r:' . $servername . ' /u:' . $username . ' /p:' . $password . ' sc query ' . $servicename . ' 2>&1';
exec($command, $output);

//Defines the server status as $servicestate which is stored in the fourth part of the command array.
//Then the string "STATE" and any number is stripped from $servicestate. This will leave only the status of the service (e.g. RUNNING or STOPPED).
$servicestate = $output[3];
$strremove = array('/STATE/','/:/','/[0-9]+/','/\s+/');
$servicestate = preg_replace($strremove, '', $servicestate);

//Define an invalid output. Sometimes the array is invalid. Catch this issue and restart the function for valid output. 
//Typically this can be caught when the string "SERVICE_NAME" is found in $output[3].
$badservicestate = "SERVICE_NAME" . $servicename;
if($servicestate == $badservicestate) {
    goto start;
}

//Service status (e.g. Running, Stopped Disabled) is returned as $servicestate.
return $servicestate;
}

1 ответ

Самое простое решение, поскольку вы вызываете внешний процесс и вам действительно нужен его вывод в сценарии, - это переписать exec с точки зрения proc_open и неблокирующий ввод / вывод:

function exec_timeout($cmd, $timeout, &$output = '') {
    $fdSpec = [
        0 => ['file', '/dev/null', 'r'], //nothing to send to child process
        1 => ['pipe', 'w'], //child process's stdout
        2 => ['file', '/dev/null', 'a'], //don't care about child process stderr
    ];
    $pipes = [];
    $proc = proc_open($cmd, $fdSpec, $pipes);
    stream_set_blocking($pipes[1], false);

    $stop = time() + $timeout;
    while(1) {
        $in = [$pipes[1]];
        $out = [];
        $err = [];
        stream_select($in, $out, $err, min(1, $stop - time()));

        if($in) {
            while(!feof($in[0])) {
                $output .= stream_get_contents($in[0]);
                break;
            }

            if(feof($in[0])) {
                break;
            }
        } else if($stop <= time()) {
            break;
        }
    }

    fclose($pipes[1]); //close process's stdout, since we're done with it 
    $status = proc_get_status($proc);
    if($status['running']) {
        proc_terminate($proc); //terminate, since close will block until the process exits itself

        return -1;
    } else {
        proc_close($proc);

        return $status['exitcode'];
    }
}

$returnValue = exec_timeout('YOUR COMMAND HERE', $timeout, $output);

Этот код:

  • использования proc_open открыть дочерний процесс. Мы указываем только трубу для ребенка stdout, так как нам нечего отправить на него, и нам плевать на его stderr выход. если вы это сделаете, вам придется соответствующим образом изменить следующий код.
  • Петли на stream_select(), который будет блокировать на период до $timeout задавать ($stop - time()).
  • Если есть вход, он будет var_dump() содержимое входного буфера. Это не будет блокировать, потому что у нас есть stream_set_blocking($pipe[1], false) на трубе. Вы, вероятно, захотите сохранить содержимое в переменную (добавив, а не перезаписав), а не распечатывая.
  • Когда мы прочитаем весь файл или превысим время ожидания, остановитесь.
  • Очистите, закрыв процесс, который мы открыли.
  • Вывод сохраняется в строке передачи по ссылке $output, Код завершения процесса возвращается, или -1 в случае тайм-аута.
Другие вопросы по тегам