Процедурное настоящее PHP

ПХП, к сожалению, в сердце ещё глубоко процедурный язык. Наткнулся на очередную особенность. Сначала небольшая предыстория.

Многие знают, что FastCGI в ПХП не настоящий — при каждом запросе весь код интерпретируется заново, это позволяет не заботиться об утечке памяти, сбросе состояния между запросами и ещё о многих вещах, а значит упрощает вообще всё, что соответствует духу ПХП — понизить до минимума порог вхождения. Конечно, это сказывается на производительности, но в некоторых случаях ПХП научился смягчать этот недостаток.

Например, есть такая вещь как «постоянные соединения» (persistent connections) — это специальные режим, в котором на уровне процесса PHP FastCGI соединения с чем-либо не закрываются и выдаются в следующий запуск кода уже открытыми. Чудесная вещь, ведь операция соединения, например, с «Ораклом» очень дорогая.

Конечно, кое-чем приходится расплачиваться — например, в открытом соединении «Оракла» остаются установленные предыдущим кодом параметры сессии, временные таблицы, незавершённые транзакции, наверное что-то ещё. Первые два пункта меня лично не пугают, так как параметры сессии у нас везде одинаковые и временные таблицы мы не используем, а вот последний — очень.

Как известно, ПХП может свалиться в фатальной ошибкой по многим причинам, одна из них — превышение времени обработки скрипта. Соответственно, скрипт может упасть и в середине транзакции, она останется незакрытой и может остаться висеть долгое время, пока соединение не получит скрипт, в котором встретится команда закрытия транзакции.

А пока этого не произошло, блокировки на базе могут привести к коллапсу.

В общем-то, есть хороший выход — у нас же есть сервис-объект для работы с базой? Вставим в деструктор принудительный откат транзакции (rollback) и все дела! Если транзакция была не завершена в силу фатальной ошибки, она откатится и всё! Ведь деструктор должен вызвааться на уничтожение объекта!

Как показывает практика, должен, но не обязан. Ниже простой код:
class ImportantToClose {
    public function __destruct()
    {
        // закрываем что-то важное
        echo "Вызвался деструктор\n";
    }
}

$itc = new ImportantToClose;

register_shutdown_function(function()  {
    echo "Вызвалась shutdown function\n";
});

set_time_limit(1);

for(;;);
Если запустить его в интерпретаторе и подождать секунду, то мы получим следующую ошибку:
PHP Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18

Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18
Вызвалась shutdown function
Можно ещё попробовать записать что-нибудь в файл, результат будет тот же — деструктор не вызовется, тогда как-то старая добрая процедурная shutdown function — да. Поэтому, если у вас есть нечто важное, что необходимо аккуратно закрывать, не делайте это в деструкторе, делайте в этой магической функции. В нашем случае, можно реализовать в каждом сервисе метод close и в этой функции вызвать этот метод для кажого открытого сервиса.

Кстати, не забудьте выставить внутри shutdown function нужное вам время выполнения, если предел будет превышен, то и эта функция будет прервана, в этом случае вам уже ничего не поможет закрыть ресурс корректно.
28 сентября 2014 11:29

dinoelq (инкогнито)
28 сентября 2014, 12:41

Ладно хоть анонимные функции внутри register_shutdown_function теперь можно использовать.

Евгений Степанищев (bolknote.ru)
28 сентября 2014, 12:57, ответ предназначен dinoelq

Это да, удобно.

Сергей Морозов (morozov.livejournal.com)
28 сентября 2014, 14:02

Есть ещё одна причина, по которой стоит использовать shutdown function вместо деструктора для важных задач. Если ваш деструктор вызывается при выходе из скрипта, не факт, что в этот момент весь объект будет в целостном состоянии. Допустим, у объекта есть свойство соединения с БД, которое (соединение) также доступно через глобальную переменную. Вполне возможно, что деструктор объекта будет вызван после того, как соедиенение закрыто и значение глобальной переменной (а также и самого свойства) очищено.

Евгений Степанищев (bolknote.ru)
28 сентября 2014, 16:11, ответ предназначен Сергей Морозов (morozov.livejournal.com):

Это просто ещё одна причина не использовать глобальные переменные :)

Сергей Морозов (morozov.livejournal.com)
28 сентября 2014, 20:54, ответ предназначен Евгений Степанищев (bolknote.ru):

В нашем случае это была статическая переменная класса (то ли синглтон, то ли реестр), но сути это не меняет, правда :)

Евгений Степанищев (bolknote.ru)
28 сентября 2014, 21:24, ответ предназначен Сергей Морозов (morozov.livejournal.com):

Ну да, если всё организовано через сервисы и сервисы передаются в другие только через входные параметры, то всё дерево зависимостей будет согласовано через ссылки друг на друга, автоматически.

Александр Макаров (инкогнито)
28 сентября 2014, 21:31

Всё хорошо, но при переполнении доступной памяти это не сработает. Приходится резервировать память заранее и чистить при фатале, чтобы хватило на нужные вызовы.

Евгений Степанищев (bolknote.ru)
28 сентября 2014, 21:39, ответ предназначен Александру Макарову

Я боюсь, что и в этом случае гарантии нет. В некоторых интерпретаторах память в этом случае резервируется самим интерпретатором.

Кстати, в «Яндексе» я писал обвязку к libmapi на «Пайтоне» с использованием ctypes, поскольку библиотека тогда была очень текучая (не знаю как сейчас), я пользовался тем же приёмом — выделял память в интерпретаторе специальным вызвовом (PyMem_Malloc), а потом освобождал в «трудную минуту».

программист (инкогнито)
31 октября 2014, 18:10

1. Есть вот такой вопрос о PHP.
Каковы внутренние ограничения по производительности самого PHP.
А именно какова корреляция между использованными аппаратными ресурсами и количеством одновременно используемых классов/объектов, количеством вызываемых методов и их вложенностью и т.д.?

2. И есть вот такой вопрос по аппаратным ресурсам. Какова стандартная аппаратно-системная конфигурация (процессоры, память, ОС и пр.) для реально сложного (скажем, просто сложного, не вдаваясь в детали) проекта на PHP, который должен обслуживать десятки и сотни тысяч параллельно работающих в системе пользователей? (Хотелось бы просто понять общие характеристики одной строкой. К примеру, в таком стиле "для прохождения этой дороги хватит и старого российского авто, здесь нужен внедорожник, а здесь и танк не пройдет".)

Спрашиваю у вас, поскольку по-моему натакой вопрос может ответить только гуру.

Евгений Степанищев (bolknote.ru)
1 ноября 2014, 11:34, ответ предназначен программисту

1) понятия не имею. В современном мире очень важна скорость разработки. У ООП в этом плане больше плюсов по сравнению с процедурным стилем, так что я выберу ООП, разве что сделаю исключения для каких-то скриптов с простым процессом внутри.

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

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)