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. Всем успехов.  

Javascript. IE or not IE

Часто при реализации того или иного скрипта необходимо учитывать вопросы кроссбраузерности, в частности - определение типа браузера. Анализ значения свойства navigator.userAgent в этом случае считается не совсем безопасным (может быть подменен пользователем), более правильнее - определение типа по наличию поддержки так называемых  условных комментариев.
Инструкция:

var isIE = /*@cc_on!@*/false;

установит переменную isIE в значение false во всех браузерах (так как они игнорируют комментарии), но только не в IE из-за оператора отрицания ! в условном комментарии. Данная инструкция воспринимается IE следующим образом:

var isIE = !false; // true

javascript. About objects

При вызове с оператором new функция-конструктор всегда возвращает объект. По умолчанию это объект, на который указывает ссылка this. Если внутри конструктора к нему не добавляются никакие свойства, возвращается "пустой" объект ("пустой", если не считать свойства и методы, унаследованные от прототипа конструктора). Конструкторы неявно возвращают значение this, даже если в них отсутствует инструкция return. Однако сохраняется возможность вернуть любой другой объект по своему усмотрению. Стоит помнить, что если вызвать конструктор без оператора new, ссылка this будет указывать на глобальный объект. В браузерах ссылка this будет указывать на объект window. Если в конструкторе создается какое-либо новое свойство, такое как this.member, то при вызове конструктора без оператора new будет создано новое свойство глобального объекта, к которому можно будет обратиться так: window.member или просто member. Чтобы этого избежать нужно использовать конструктор иначе.

function Person(name) {
    var that = {};
    that.name = name || '';
    return that;
}


Но в этом случае будет утрачена связь с прототипом, поэтому все члены, добавленные к прототипу функции Person, окажутся недоступными для объектов. Чтобы этого избежать, нужно немного видоизменить конструктор:

function Person(name) {

    if (!(this instanceof Person)) {
        return new Person(name);
    }
   
    this.name = name || '';

}

Person.prototype.getName = function () {
    return this.name;
};

var Darvin = Person('Darvin');
console.log(Darvin.getName()); // Darvin

var Einstein = new Person('Einstein');
console.log(Einstein.getName()); // Einstein


Как видно из примера, все работает как положено.
Вообще стоит подметить, при каждом вызове конструктора в памяти создаются новые функции и новые переменные (свойства). Совершенно очевидно, что это неэкономный подход к расходованию памяти, потому что реализация не изменяется от одного экземпляра к другому. Поэтому эффективней добавлять такие методы и свойства к прототипу функции-конструктора.

MyConstructor.prototype.property = true;
 

MyConstructor.prototype.getProperty = function () {
    return this.property;
};


Любой объект имеет свойство constructor, это ссылка на конструктор данного объекта:

var Adam = new Person('Adam');
var Eva = new Person('Eva');
var Cain = new Eva.constructor('Cain'); // здесь мы обращаемся к конструктору Person

Также можно обращаться и к прототипу конструктора:

Cain.constructor.prototype.child = true;

Создание объектов в форме литералов очень удобно, но при этом теряется возможность проверок следующего типа. Выше был создан объект Adam, конструктором которого является Person, значит в коде можно использовать следующую проверку:

Adam instanceof Person // true

Для объектов в форме литералов конструктор является анонимным, то есть так проверить на соответствие типу не удастся. Стоит также подметить. что в случае каскадного наследования объекты также могут подвергаться проверке, например:

var Mammal = function () {
    this.name = 'mammal';
    this.getName = function () {
        return this.name;
    };
};

var Dog = function (name) {
    this.name = name || 'dog';
};

Dog.prototype = new Mammal;

var Bob = new Dog('Bob');

console.log(Bob instanceof Dog); // true
console.log(Bob instanceof Mammal); // true


То есть проверка проходит не только по своему типу. но и по родительскому. На последок небольшая схемка. Всем успехов в Ваших делах.


javascript. Подъем функций

В javascript функцию можно построить либо на объявлении

function foo() {
  //...
}

либо на выражении

var bar = function () {
  //...
};

Функции-выражения подчеркивают тот факт, что они являются объектами. Вроде бы на этом отличий нет, но это не так. Выполним следующий кусок кода.

foo();

function foo() {
    console.log('function - declaration');
}


На выходе получим сообщение - function - declaration - то есть несмотря на то, что функция определена ниже места ее вызова, интерпретатор без проблем выполнит ее, это называется подъемом функций. Попробуем выполнить такой же кусочек, но только на основе функции-выражения.

foo();

var foo = function () {
    console.log('function - expression');
};


Хоп, на выходе получаем сообщение об ошибке

TypeError: foo is not a function

То есть в данном случае поднятия определения функции не произошло, в отличии от имени. Вот еще пример с поднятием. И в этом случае :

foo();

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};


и в этом:

foo();

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}


мы будем получать сообщение - function - declaration. Но вот если вызов происходит ниже, то картина меняется на оборот:

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}

foo();


или вот так:

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};

foo();


мы будем получать сообщение - function - expression.
Правило подъема действует и внутри самих функций. Вот небольшой примерчик:

bar();

function bar() {


    (function (global) {
        global.foo();
        foo();
    })(window);
   
    function foo() {
        console.log('local function - declaration');
    }

   
}

function foo() {
    console.log('global function - declaration');
}


На выходе получим следующие сообщения:

global function - declaration
local function - declaration
global function - declaration
local function - declaration
global function - declaration
local function - declaration

javascript. Механизм подстановки точки с запятой

Знали ли Вы что интерпретатор javascript наделен механизмом подстановки точки с запятой в конце каждой строки. Причем этот механизм работает за Вашей спиной. Поэтому, там где это должны сделать Вы, то и делайте это сами, так как тут можно столкнуться с необычными результатами. Рассмотрим пример. Напишем функцию-выражение, которая будет возвращать простой объект:

var object = function (name) {
    return
    {
        name: name || ''
    };
};


Вроде ничего такого. Но это на первый взгляд. Попробуйте получить от нее обещанный объект:

var obj = object('Leonardo'); // undefined

И если мы заглянем в этот объект, то окажется, что он не определен (возвращается undefined результат). Это связано с тем самым механизмом авто подстановки, которое после return ставит те самые точку с запятой. И в итоге неопределенное значение. Поэтому, чтобы пример отработал как положено, необходимо немного ее видоизменить:

var object = function (name) {
    return {
        name: name || ''
    };
};


Теперь все работает :)