22 фатала за две недели: как count(null) в бизнес-процессах свалил CRM под PHP 8
Корпоративный портал Битрикс24 неделями работал стабильно, а потом менеджеры начали ловить 500 при сохранении сделок. Разбираем редкий каскадный сбой: короткая просадка MySQL, самовызовы REST в авто-бизнес-процессах и count() по null, который на PHP 8 стал фатальной ошибкой.
Симптом
Коробочный Битрикс24 (редакция на 100 пользователей, on-prem) неделями работал ровно — и вдруг менеджеры начали ловить 500 Internal Server Error при сохранении лидов и сделок. Падало на calculateTotalData и /bitrix/tools/uf.php, а вместе с ошибкой молча переставали отрабатывать привязка компании и контакта и автоматическая смена стадий.
Разведка
В логах — Uncaught TypeError: count(): Argument #1 must be of type Countable|array, null given. Источник — три авто-стартующих бизнес-процесса: поиск контрагентов на лиде, поиск звонков и смена стадий на сделке. Все три написаны через PHP-активности, и каждая дёргала свой же REST блокирующим curl-самовызовом на тот же сервер, после чего делала count($result['result']).
Причина
Каскад собирался так: кратковременная просадка базы (Mysql connect error (2002)) → упирание в MaxRequestWorkers → самовызовы REST не могут достучаться до сервера → $result['result'] приходит null → count(null). На PHP 7 это дало бы warning и ноль, на PHP 8 — фатал. Осиротевшие состояния воркфлоу копились, догружали сервер, и цикл повторялся.
Масштаб «мусора» показал, насколько давно это тлело: b_bp_workflow_state — 280 786 строк (280 415 осиротевших), b_bp_tracking — 309 170, b_bp_workflow_filter — 296 445. Суммарно около 450 МБ мёртвых воркфлоу при всего двух живых инстансах.
Что сделали
Первым делом остановили петлю — погасили авто-старт у трёх шаблонов (AUTO_EXECUTE=0), не удаляя сами шаблоны, и сбросили managed-кэш. Перед правкой сняли бэкап шаблонов. Каскад прекратился сразу.
Дальше предложили два уровня фикса: быстрый null-guard (is_array($result['result'] ?? null) ? count(...) : 0) и правильный — заменить блокирующие curl-самовызовы прямым PHP-API (CCrmCompany::GetList и т.п.), что убирает и count(null), и саму петлю самовызова.
Результат
За две недели до вмешательства — 22 фатала (каждый = видимая пользователю 500). После гашения авто-старта петля остановлена, новые падения прекратились. Это тот случай, где ценность мониторинга не в «сайт лежит», а в том, что редкий каскад из четырёх звеньев разобран по логам до первопричины — а не залечен рестартом.
Имя клиента не раскрываем по NDA. Цифры и подход — реальные.