PHP. Время работы "тяжелых" скриптов

Как долго продолжают работать "тяжелые" скрипты, если не дожидаясь окончания их работы закрыть браузер? И продолжат ли они свою работу? Как показали наблюдения, они продолжают жить пытаясь выполнить поставленную перед ними работу. Многие для управления вот такого рода временем "темной" жизни скриптов прибегают к инструкции вида  

ignore_user_abort(boolean mode)

Если эта инструкция вызывается с false параметром, то она равнозначна ее отсутствию. Обычно она указывается с параметром true - ignore_user_abort (true). Это необходимо в случаях, когда нужно продолжить работу скрипта, даже если браузер сбросил соединение. Вообще в php имеются возможности для отслеживания состояния подключения. Меня заинтересовала ситуация с продолжением выполнения скрипта даже после закрытия окна браузера. Проверить это очень просто: напишите скриптик, который выполняется некоторое время, запустите его и недожидаясь окончания работы скрипта закройте окно браузера, не забудьте в скрипте добавить маркер завершения работы, например создать файлик. Так вы сможете отслеживать за выполняемостью вашего скриптика. Вообще после закрытия сеанса агента (браузера) скрипт должен умереть, но как вы можете убедиться, это не совсем так: он все же пытается прожить подольше. В этом случае есть смысл перенести его в фоновое выполнение (cron). Но все же интересно было проанализировать данную особенность. Для отслеживания состояния подключения используется инструкция connection_status(). Для дальнейших манипуляций были написаны свои обертки:

// return code of connect status
function getConnectStatus() {
    return connection_status();
}

// return type of connect status by code
function getConnectStatusByCode($connectionStatus) {
    switch ($connectionStatus) {
        case CONNECTION_NORMAL:
           $status = 'Normal';
           break;
        case CONNECTION_ABORTED:
           $status = 'User Abort';
           break;
        case CONNECTION_TIMEOUT:

           $status = 'Max Execution Time exceeded';
           break;
        case (CONNECTION_ABORTED & CONNECTION_TIMEOUT):
           $status = 'Aborted and Timed Out';
           break;
        default:
           $status = 'Unknown';
           break;
    }      
    return $status;
}

// check the connect status (true - normal, false - abnormal)
function checkConnectionStatus() {
    return connection_status() === 0;
}


Теперь можно написать функцию, которая будет имитировать какую-либо долгую работу

// imitation long script
function scriptImitation() {  
  
    $start = time();
  
    $limitIteration = 1000000;
    for ($i = 0; checkConnectionStatus() && ($i < $limitIteration); $i++) {
        // some long work...
    }
  
    $end = time();
    $runTime = $end - $start;
  
    if ($i === $limitIteration || !checkConnectionStatus()) {
        $connectStatus = getConnectStatusByCode(getConnectStatus());
        $scriptImitationLog  = "Connect status: $connectStatus";
        $scriptImitationLog .= "; ";
        $scriptImitationLog .= "Iteration: $i";
        $scriptImitationLog .= "; ";
        $scriptImitationLog .= "Run time: $runTime second";
      
        file_put_contents('scriptImitationLog.txt', $scriptImitationLog);
    }
  
}


Собственно дальше периодически запускаем с остановами работу нашего скрипта. Как бы быстро не пытался закрыть окно агента, скрипт все также успешно отрабатывал и на выходе создавался лог. Не стоит забывать про max_execution_time, дольше этого времени скрипт точно не будет жить. В нашем случае он был увеличин. Давайте заглянем в лог:

Connect status: Normal; Iteration: 1000000; Run time: 5 second

Как видно, подключение в норме, цикл отработал до конца. Так в чем же дело? В этой статье на stackoverflow нашел вот такое решение - http://stackoverflow.com/questions/2389035/php-connection-status. И связано оно было с буфером вывода: периодически в буфер вывода скрипта дописывается небольшая порция данных (например, пустой символ) с последующим сбросом (обычно для php подразумевается принудительная отправка всего содержимого с последующей очисткой). Для этого предлагается использовать совместное использование функций ob_flush и flush. И так как для сброса подразумевается наличие подключения (ведь буфер формируется для отправки в направлении браузера - инициатора), то при очередном сбросе при отсутствии нормального подключения его статус переводится в иное состояние (User aborted). Причем если в скрипте отсутствует инструкция или приведена в виде

ignore_user_abort (false);

тогда при изменении нормального статуса подключения скрипт тут же перестает выполняться, в случае наличия инструкции

ignore_user_abort (true);

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

// check the connect status (true - normal, false - abnormal)
function checkConnectionStatus() {
    print " ";
    ob_flush();
    flush();
    return connection_status() === 0;
}


Теперь все находится под контролем, и в случаях, когда мы закроем наш браузер, работа скрипта будет приостановлена или можно будет организовать иное поведение.
На последок приведу результаты еще одного наблюдения. Если сравнить время выполнения скрипта по логам до внесения изменений и после в обертку - checkConnectionStatus,
можно сделать вывод, что сброс буфера вывода занимает продолжительное время. Так время на одну итерацию в тестовом скрипте без сброса буфера в среднем заняло - 0,005 ms, со сбросом - 0,028 ms, то есть сброс буфера в среднем заняло 0,023 ms, что в 4,6 раза больше времени одной итерации. Отсюда видно, что приведенный метод отслеживания за жизнью работы "тяжелых" скриптов может увеличивать основное время работы. Данный тест проводился на Firefox version 29.0.1. Всем успехов.