Обнаружение таймаута для блока кода в PHP
Есть ли способ прервать блок кода, если он слишком длится в PHP? Возможно, что-то вроде:
//Set the max time to 2 seconds
$time = new TimeOut(2);
$time->startTime();
sleep(3)
$time->endTime();
if ($time->timeExpired()){
echo 'This function took too long to execute and was aborted.';
}
Это не должно быть точно так же, как указано выше, но существуют ли какие-либо собственные PHP-функции или классы, которые делают что-то вроде этого?
Изменить: ответ Ben Lee с помощью pcnt_fork
будет идеальным решением, за исключением того, что он недоступен для Windows. Есть ли другой способ сделать это с помощью PHP, который работает для Windows и Linux, но не требует внешней библиотеки?
Изменить 2. Решение XzKto работает в некоторых случаях, но не последовательно, и я не могу, похоже, поймать исключение, независимо от того, что я пробую. Вариант использования - это время ожидания для unit test. Если тест истечет, я хочу его закончить, а затем перейти к следующему тесту.
Ответы
Ответ 1
Вы можете сделать это, развернув процесс, а затем используя родительский процесс для мониторинга дочернего процесса. pcntl_fork - это метод, который обрабатывает процесс, поэтому у вас есть две почти идентичные программы в памяти, работающие параллельно. Единственное различие заключается в том, что в одном процессе родительский pcntl_fork
возвращает положительное целое число, соответствующее идентификатору процесса дочернего процесса. А в другом процессе дочерний элемент pcntl_fork
возвращает 0.
Вот пример:
$pid = pcntl_fork();
if ($pid == 0) {
// this is the child process
} else {
// this is the parent process, and we know the child process id is in $pid
}
Это основная структура. Следующий шаг - добавить истечение процесса. Ваш материал будет запущен в дочернем процессе, и родительский процесс будет отвечать только за мониторинг и синхронизацию дочернего процесса. Но для того, чтобы один процесс (родительский) убил другого (ребенка), должен быть сигнал. Сигналы - это то, как взаимодействуют процессы, а сигнал, который означает "вы должны немедленно прекратить", - это SIGKILL. Вы можете отправить этот сигнал с помощью posix_kill. Таким образом, родитель должен просто ждать 2 секунды, а затем убить его, например:
$pid = pcntl_fork();
if ($pid == 0) {
// this is the child process
// run your potentially time-consuming method
} else {
// this is the parent process, and we know the child process id is in $pid
sleep(2); // wait 2 seconds
posix_kill($pid, SIGKILL); // then kill the child
}
Ответ 2
Вы не можете этого сделать, если вы script останавливается на одной команде (например, sleep()), кроме того, что используется для форсирования, но для особых случаев существует много задач: например, асинхронные запросы, если вы программируете в DB query, proc_open, если программа приостанавливается при некотором внешнем исполнении и т.д. К сожалению, они все разные, поэтому нет общего решения.
Если вы script ожидаете длинный цикл/много строк кода, вы можете сделать такой трюк:
declare(ticks=1);
class Timouter {
private static $start_time = false,
$timeout;
public static function start($timeout) {
self::$start_time = microtime(true);
self::$timeout = (float) $timeout;
register_tick_function(array('Timouter', 'tick'));
}
public static function end() {
unregister_tick_function(array('Timouter', 'tick'));
}
public static function tick() {
if ((microtime(true) - self::$start_time) > self::$timeout)
throw new Exception;
}
}
//Main code
try {
//Start timeout
Timouter::start(3);
//Some long code to execute that you want to set timeout for.
while (1);
} catch (Exception $e) {
Timouter::end();
echo "Timeouted!";
}
но я не думаю, что это очень хорошо. Если вы укажете точный случай, я думаю, мы можем помочь вам лучше.
Ответ 3
Вы можете использовать функцию объявления, если время выполнения превышает пределы. http://www.php.net/manual/en/control-structures.declare.php
Вот пример кода, как использовать
define("MAX_EXECUTION_TIME", 2); # seconds
$timeline = time() + MAX_EXECUTION_TIME;
function check_timeout()
{
if( time() < $GLOBALS['timeline'] ) return;
# timeout reached:
print "Timeout!".PHP_EOL;
exit;
}
register_tick_function("check_timeout");
$data = "";
declare( ticks=1 ){
# here the process that might require long execution time
sleep(5); // Comment this line to see this data text
$data = "Long process result".PHP_EOL;
}
# Ok, process completed, output the result:
print $data;
С помощью этого кода вы увидите сообщение с таймаутом.
Если вы хотите получить результат Long process внутри блока declare, вы можете просто удалить строку sleep (5) или увеличить максимальное время выполнения, объявленное в начале script
Ответ 4
Как насчет set-time-limit, если вы не находитесь в безопасном режиме.
Ответ 5
Приготовил это примерно через две минуты, я забыл называть $time->startTime();
, поэтому я точно не знаю, сколько времени прошло;)
class TimeOut{
public function __construct($time=0)
{
$this->limit = $time;
}
public function startTime()
{
$this->old = microtime(true);
}
public function checkTime()
{
$this->new = microtime(true);
}
public function timeExpired()
{
$this->checkTime();
return ($this->new - $this->old > $this->limit);
}
}
И демонстрация.
Я действительно не понимаю, что делает ваш вызов endTime()
, поэтому я сделал вместо checkTime()
, что также не имеет реальной цели, а обновляет внутренние значения. timeExpired()
вызывает это автоматически, потому что он наверняка воняет, если вы забыли позвонить checkTime()
, и он использовал старые времена.
Ответ 6
Это старый вопрос и, вероятно, уже много раз был решен, но для людей, которые ищут простой способ решить эту проблему, теперь есть библиотека: PHP Invoker.
Ответ 7
Вы также можете использовать второй script, который имеет в нем код паузы, который выполняется через завиток с установленным таймаутом. Другим очевидным решением является устранение причины паузы.