Подтвердить что ты не робот

Обнаружение таймаута для блока кода в 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. Если тест истечет, я хочу его закончить, а затем перейти к следующему тесту.

4b9b3361

Ответ 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, который имеет в нем код паузы, который выполняется через завиток с установленным таймаутом. Другим очевидным решением является устранение причины паузы.