- Битрикс: предупреждение «Размер стека и pcre.recursion_limit»
- Стабилизируем PHP на бою — что и почему «роняет» веб-сервер
- Ошибки серверного софта
- Нестабильная работа серверных скриптов
- Смотрим в лог ошибок веб-сервера
- А за что убили то?
- На месте преступления — толкуем coredump
- Отладка PHP в gdb — черная магия
- Распространенные причины ошибок
- Отладка запущенного процесса
- Итоги
Битрикс: предупреждение «Размер стека и pcre.recursion_limit»
Исходные данные: сервер с Debian 10 + панель ISP Manager, версия PHP — старше 7.0.
Запускаем проверку системы — /bitrix/admin/site_checker.php .
В результате, в пункте «Размер стека и pcre.recursion_limit» видим предупреждение: «Замечание. Возможны проблемы в работе с длинными строками из-за системных ограничений». Детальный текст, призванный видимо нам помочь, выглядит так:
Если параметр pcre.recursion_limit превышает системный размер стека (обычно 8 Мб), то PHP падает с ошибкой Segmentation fault при выполнении сложных регулярных выражений.
Если нет возможности увеличить системный размер стека, необходимо уменьшить параметр pcre.recursion_limit. В этом случае PHP падать не будет, но обработка строк будет работать не всегда правильно: могут появляться пустые сообщения на форуме.
В общем-то все понятно, кроме того, что далее со всем этим делать. Предлагаемые гуглом и форумом БУС решения в моем случае не сработали, однако решается данная проблема крайне просто.
В настройках PHP находим переменную pcre.jit . По умолчанию она включена, т.е. имеет значение 1. Все что нам нужно сделать — установить значение равное нулю (pcre.jit=0).
Для верности — можно перезапустить службу Apache, но в моем случае этого не потребовалось.
Запускаем повторную проверку, проверяем, что замечание исчезло:
Стабилизируем PHP на бою — что и почему «роняет» веб-сервер
Вы отвечаете за стабильность работы веб-проекта на PHP. Нагрузка постоянно растет, добавляются фичи, клиенты довольны. В один прекрасный день начинают появляться загадочные ошибки…
Ошибки серверного софта
… которые программисты не знают как исправить, т.к. «ломается» серверный софт, например связка apache-PHP — а клиент получает в ответ на запрос страницу о регламентных работах. Веб-разработчик часто не обладает глубокими знаниями в программировании на C в unix/linux, а сисадмин нередко, к сожалению, глубже bash в систему не погружается. Настоящий хардкор 🙂
Нестабильная работа серверных скриптов
Нередко, определенные страницы веб-проекта начинают сходить с ума. Например выполняться по 15 минут и выяснить, чем же они занимаются, непросто. В прошлом посте на данную тему я описал одну из методик определения, чем занимается PHP-скрипт на боевом сервере, но чувствуется, что нужен более мощный инструмент.
На практике я часто встречаю проекты, которые сталкиваются с подобным классом ошибок «серверного софта», и в команде не всегда знают, что делать. В логе apache часто появляются сообщения о нарушении сегментации (segmentation fault), клиенты получают страницу об ошибке, а веб-разработчик с сисадмином ломают себе голову, играются с разными версиями PHP/apache/прекомпилятора, собирают PHP из исходников с разными опциями снова и снова, пишут о багах, а им доказывают, что это баги не PHP, а их кода и так до бесконечности…
В статье я хочу рассказать как можно просто и быстро найти причину, почему PHP рассыпался на боевом сервере и устранить ее — не погружаясь в прекрасный мир системного программирования на C для unix 🙂 От вас потребуется желание и одна чашечка кофе.
Смотрим в лог ошибок веб-сервера
Если в логе ошибок apache вы видите что-то подобное, то статья для вас:
[Mon Oct 01 12:32:09 2012] [notice] child pid 27120 exit signal Segmentation fault (11)
В данном случае бесполезно искать подробную информацию в логе ошибок PHP — ведь грохнулся сам процесс, а не скрипт. Если заранее не сделать на nginx симпатичную страничку о регламентных работах, то клиенты увидят аскетичную ошибку «50*».
Хочется дать кому-нибудь в морду, но кому? 🙂 Чтобы отвлечься от деструктивных решений, вспомним теорию.
Что такое «signal»? Это, можно сказать, средство, которое операционная система использует, чтобы сказать процессу, что он, например, не прав 🙂 Берет и, нарушая законы математики, делит на… 0, или насильственными действиями вызывает переполнение стека. В данном случае мы видим сигнал с номером 11 и названием «SIGSEGV». Список сигналов можно посмотреть, выполнив «kill -l»:
.
11) SIGSEGV
.
Некоторые сигналы, например SIGSEGV — нельзя перехватить, поэтому ваш процесс apache-PHP будет безжалостно убит ядром без суда и следствия. Оказывается именно его перехватить — можно, но нужно лезть в исходники 🙂
А за что убили то?
Теперь найдем причину, за что же убили процесс apache-PHP? Для этого нужно настроить создание дампа памяти процесса в момент убийства 🙂 или coredump. Да, да — до сих пор используется устаревший лет эдак на 50 термин, означающий сохранение данных с магнитных сердечников. Как только в следующий раз процесс будет убит операционной системой, будет ядром создан файл — место и его название можно настроить. Если вы в консоли, просто наберите «man 5 core».
Например, можно складывать файлы в папочку так:
echo «/tmp/httpd-core.%p» > /proc/sys/kernel/core_pattern
Если ничего не задать, система создаст файл с именем «core.#process_number#» в рабочей директории процесса.
Только проследите, чтобы процесс apache-PHP имел туда право записи.
Это еще не всё. По-умолчанию, скорее всего, в вашей системе отключена генерация coredump-файлов. Ее можно включить, вставив в начало скрипта запуска веб-сервера строку:
ulimit -с unlimited
или, чтобы сделать настройку постоянной, отредактировать файлик «/etc/security/limits.conf». Туда можно вставить:
apache — core -1
Подробности по формату файла — » man limits.conf».
Однако, пока я для apache не настроил папку для coredump-файлов, ничего не работало («/etc/httpd/conf/httpd.conf»):
CoreDumpDirectory /tmp
Теперь перезапускаем апач:
service httpd restart
Тестируем. Вручную убьем процесс:
ps aux | grep httpd
…
kill -11 12345
Смотрим в «/var/log/httpd/error_log»:
[Mon Oct 01 16:12:08 2012] [notice] child pid 22596 exit signal Segmentation fault (11), possible coredump in /tmp
В «/tmp» теперь найдем файлик с названием типа «/tmp/httpd-core.22596»
Вы научились получать дамп памяти убитого процесса. Теперь ждем, когда процесс будет убит естественным образом.
На месте преступления — толкуем coredump
Важно знать, что если PHP собрана без отладочных символов (ключик —enable-debug, -g для gcc при компиляции) — мы потеряем много полезной информации. Однако, если вы собрали PHP из исходников даже без этой опции, и исходники лежат рядом — этого может хватить для анализа.
Еще есть очень распространенное заблуждение, что отладочная сборка влияет на производительность и потребляемую процессом память (memory footprint). Не влияет, а лишь увеличивается размер исполняемого файла. Поэтому, если не сможете разобраться в причине ошибки без отладочной сборки — попросите сисадмина собрать модуль PHP с отладочными символами.
Чем открыть coredump? Конечно старой и «очень доброй» утилитой — gdb, изначально написанной верховным апостолом движения бесплатного свободного программного обеспечения Ричардом Столманом.
Разобраться, как работает отладчик, не займет много времени. Можно за пару часиков поглотить один из самых занимательных учебников, а можно попросить это сделать сисадмина 😉
Обычно открывают coredump так:
gdb путь_к_выполняемому_файлу_веб-сервера путь_к_coredump
Все уважающие себя разработчики на C в unix конечно умеют пользоваться этим отладчиком, делают это, наверное, каждый день, но, к сожалению, их может не быть в вашей команде. И есть еще одно неприятное НО…
Отладка PHP в gdb — черная магия
Дело в том, что скомпилированный в байткод скрипт PHP это… не совсем программа на C 😉 Нужно, правда совсем немного,
разобраться во внутренностях движка Zend — и вы все поймете довольно быстро. А именно — нужно найти в трейсе последний вызов функции execute, перейти в этот frame стека и исследовать локальные переменные (op_array), а также заглянуть в глобальные переменные движка Zend:
(gdb) frame 3 #3 0x080f1cc4 in execute (op_array=0x816c670) at ./zend_execute.c:1605 (gdb) print (char *)(executor_globals.function_state_ptr->function)->common.function_name $14 = 0x80fa6fa "pg_result_error" (gdb) print (char *)executor_globals.active_op_array->function_name $15 = 0x816cfc4 "result_error" (gdb) print (char *)executor_globals.active_op_array->filename $16 = 0x816afbc "/home/yohgaki/php/DEV/segfault.php"
В op_array можно запутаться, поэтому полезна команда просмотра типа этой структуры:
(gdb) ptype op_array type = struct _zend_op_array < zend_uchar type; char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; zend_uint *refcount; zend_op *opcodes; zend_uint last; zend_uint size; zend_compiled_variable *vars; int last_var; int size_var; zend_uint T; zend_brk_cont_element *brk_cont_array; zend_uint last_brk_cont; zend_uint current_brk_cont; zend_try_catch_element *try_catch_array; int last_try_catch; HashTable *static_variables; zend_op *start_op; int backpatch_count; zend_bool done_pass_two; zend_bool uses_this; char *filename; zend_uint line_start; zend_uint line_end; char *doc_comment; zend_uint doc_comment_len; void *reserved[4]; >*
Процесс отладки заключается в хождении между фреймами стека («frame N»), переходе в каждый вызов функции «execute» и исследовании ее локальных аргументов («print name», «ptype name»). Чем меньше номер фрейма, тем вы глубже. Иногда полезно зайти в гости в экстеншн PHP и посмотреть, где произошла ошибка и почему (хотя бы попытаться понять причину).
(gdb) frame #номер# (gdb) print op_array.function_name $1 = 0x2aaab7ca0c10 "myFunction" (gdb) print op_array.filename $2 = 0x2aaab7ca0c20 "/var/www/file.php"
Если вы поперхнулись кофе :-), то просто запомните, что переходя между фреймами стека вызовов с помощью команды «frame #N#», можно смотреть всего определенные элементы этой структуры — и вы точно сможете установить в каком файле PHP была вызвана функция PHP, какую функцию она вызвала и т.п. — и доберетесь до причины «Segmentation Fault» или другой ошибки, убившей процесс. И объясните программистам — в чем причина и ее поправят! Быстро и, надо быть оптимистами — навсегда.
Распространенные причины ошибок
Начните просматривать coredump-файлы (или поручите это сисадмину) и вы довольно быстро научитесь классифицировать ошибки по группам:
1) Проблемы в расширениях PHP. В этом случае либо отключите расширение, либо попробуйте поиграть его настройками. Вы точно знаете, что проблема в нем, дело за малым.
2) Проблема с рекурсией, стеком. Вы можете наступить на ошибку, при которой функция библиотеки, например, pcre, входит в рекурсию и вызывает себя тысяч двадцать раз. Можно либо настроить параметры библиотеки или, если лень, добавить процессу побольше стека («/etc/init.d/httpd»):
ulimit -s «ставим значение больше»
А текущее значение можно посмотреть командой: «ulimit -a» (man ulimit, далее ищем «ulimit»).
3) Проблемы в ядре PHP — тут нужно писать разработчикам PHP 🙂
В общем, круг причин ошибки будет серьезно сокращен. Что нам и нужно.
Отладка запущенного процесса
Это еще не все. Если вы не можете получить coredump — можно подключиться к запущенному процессу и погулять по нему. Пока вы внутри процесса, его выполнение приостанавливается («ps aux | grep apache | grep ‘T ‘» — он будет в состоянии трейсинга). Когда покинете его — он снова продолжит выполняться. Подключиться можно так:
gdb -p ид_процесса
Итоги
В статье мы научились «правильно готовить» ошибки серверного софта, делать отладочные сборки apache-PHP, создавать coredump-файлы и правильно их толковать, используя символьный отладчик. Еще мы узнали, что из coredump-файла можно найти конкретный файл PHP и функцию, вызвавшую ошибку.
Теперь можно составить чеклист для менеджера для борьбы с загадочными серверными ошибками, в которых не могут разобраться ни веб-разработчики, ни сисадмины:
- Включить сбор coredump-файлов на сервере (сисадмин)
- При необходимости пересобрать apache-PHP с отладочными символами (сисадмин)
- С помощью gdb (выходные на его изучение) исследовать причину появления ошибки (сисадмин с веб-разработчиком)
- Принять меры по ее устранению или снижению частоты появления: поменять настройки, обновить софт, написать в багтрекер, отключить расширение PHP и т.п.
В заключение приглашаю всех на наш облачный сервис Битрикс24, в котором мы эффективно используем все описанные в статье технологии.
Всем удачи и стабильной работы веб-проектов!