Реклама
Реклама
Реклама
Реклама. Zarub Zarub - зарубежные сервисы, оплата в рублях

Bitrix Application::addBackgroundJob: код после ответа, сессия и ловушки

Нужно отдать страницу или AJAX-ответ быстро, а тяжёлую мелочь (лог, webhook, пересчёт счётчика, письмо) добить уже после того, как браузер получил контент. В D7 для этого есть Application::addBackgroundJob(). Это не Redis-очередь и не замена cron: job живёт в том же PHP-процессе и выполняется сразу после отдачи ответа.

Рядом по теме: Session и LocalSession, агенты CAgent и cron, Application, Context, Request, Mail\\Event: send и sendImmediate.

Если нужна помощь с модулем или разбором «фоновых» задач на проде — услуги и контакты на karoche.ru.

Что это и чем не является

Официальная формулировка API: метод добавляет фоновое задание, которое выполняется после отдачи контента браузеру. Метод нестатический — вызывайте через Application::getInstance().

Документация BitrixFramework уточняет контракт жёстче:

  1. Задача ставится в очередь текущего запроса.
  2. После отправки контента пользователю система выполняет задачу.
  3. Ответ сервера при этом не блокируется ожиданием job.
  4. Выполнение не гарантируется: при аварийном завершении скрипта job может не выполниться.

Отсюда границы:

Ожидание Реальность
«Уйдёт в воркер Redis/Rabbit» Нет, тот же PHP-процесс после ответа
«Переживёт падение FPM» Нет, durable-хранилища нет
«Можно писать в $_SESSION» Обычно нет: сессия уже закрыта
«Замена агента раз в час» Нет, для расписания — CAgent/cron
«Есть ретраи и backoff» Нет; для этого смотрите Messenger (alpha) или свою очередь

Подходит для короткого хвоста: метрика, лёгкий webhook, дописать лог, дернуть CAgent::CheckAgents с низким приоритетом (так делает пример в API). Не подходит для генерации PDF на минуту, массового импорта и всего, что нельзя потерять молча.

Минимальный контракт

Сигнатура по D7 API:

<?php
public function addBackgroundJob(
    callable $job,
    array $args = [],
    int $priority = \Bitrix\Main\Application::JOB_PRIORITY_NORMAL
);

Приоритеты в классе Application:

  • JOB_PRIORITY_NORMAL = 100 — значение по умолчанию;
  • JOB_PRIORITY_LOW = 50.

Базовый вызов:

<?php
$app = \Bitrix\Main\Application::getInstance();

$app->addBackgroundJob(
    [\Vendor\Module\Service\Notifier::class, 'ping'],
    [15420],
    \Bitrix\Main\Application::JOB_PRIORITY_NORMAL
);

Пример из официальной документации API — проверка агентов после ответа:

<?php
\Bitrix\Main\Application::getInstance()->addBackgroundJob(
    ["CAgent", "CheckAgents"],
    [],
    \Bitrix\Main\Application::JOB_PRIORITY_LOW
);

Callable принимается в привычных PHP-формах: имя функции, [$object, 'method'], [ClassName::class, 'staticMethod'], замыкание. Для замыканий есть отдельная ловушка с use — ниже.

В Framework-доке есть сценарий с файлом: сначала сохранить upload во временный путь на диске, потом в job передать путь и имя — не держать большой бинарник в памяти до конца ответа без нужды.

Когда BackgroundJob, когда агент, когда Messenger

На karoche уже есть разбор агентов (таблица b_agent, хиты, cron). Короткая матрица выбора:

Задача Инструмент
Короткий хвост текущего хита (лог, метрика, лёгкий webhook) addBackgroundJob
Повторять каждый час / сутки CAgent (+ лучше cron, не только хиты)
Долгая/гарантированная работа с ретраями Bitrix\Main\Messenger (с main 25.100.300, alpha, без обещания BC) или внешняя очередь
Письмо «прямо сейчас» в том же хите Mail\Event::sendImmediate (см. статью про Mail\\Event)
Письмо можно отложить до эпилога/почтовика Mail\Event::send или BackgroundJob вокруг своей отправки

Messenger официально: сообщение кладётся в брокер (сейчас тип db), обработчик забирает его; режим run_mode = web или cli (php bitrix.php messenger:consume); есть retry_strategy. Это уже другая архитектура. Достаточно помнить: если нельзя потерять job при падении воркера — BackgroundJob не ваш инструмент.

Комбинация из framework-дока допустима: BackgroundJob регистрирует одноразового агента для длинного поэтапного процесса. Так вы не держите FPM на длинной работе в хвосте того же запроса.

Сессия закрыта, master-only и аргументы

К моменту запуска background jobs ядро уже отдало ответ. В типичном terminate() перед пулом jobs сессия закрывается (session_write_close / штатное сохранение), а connection pool часто переключается в режим «только мастер». Практические следствия для кода модуля:

  1. Не пишите в $_SESSION / getSession() из job. Данные либо не сохранятся, либо вы получите сюрприз на следующем хите. Нужен флаг пользователю — передайте id в $args и пишите в БД/кеш/LocalSession до постановки job, либо обновляйте сущность в БД из самого job.
  2. Все нужные скаляры кладите в $args. Не рассчитывайте, что замыкание «увидит» живой $USER и открытую сессию так же, как в основном хите.
  3. Замыкание с use по ссылке на огромный массив удержит память до конца job. Лучше id сущности и повторное чтение из БД внутри метода.
  4. Реплика: job может принудительно идти на master. Не стройте в job «чтение со слегка отстающей реплики как источник истины» без явной проверки своего окружения.

Связка с Session/LocalSession: LocalSession тоже не предназначен как «очередь после ответа». Для post-response работы передавайте аргументы явно.

Плохо:

<?php
$orderId = 15420;
$app = \Bitrix\Main\Application::getInstance();
$session = $app->getSession();

$app->addBackgroundJob(function () use ($orderId, $session) {
    // сессия уже закрыта - set здесь не то место
    $session->set('last_pinged_order', $orderId);
    // ...
});

Лучше:

<?php
$app = \Bitrix\Main\Application::getInstance();

$app->addBackgroundJob(
    [\Vendor\Module\Service\OrderPing::class, 'run'],
    [15420]
);

// Vendor\Module\Service\OrderPing.php
final class OrderPing
{
    public static function run(int $orderId): void
    {
        // читаем заказ из БД, шлём webhook, пишем свой лог-таблицу
    }
}

Сигнатура call_user_func_array разворачивает $args как список параметров. Если метод ждёт один массив — передайте один элемент: [$payload]. Если ждёт именованные поля как отдельные аргументы — следите за порядком в массиве $args (числовые ключи 0..n). Для массива вида [15420] удобнее метод с одним аргументом-массивом или явный список [15420].

Практичный безопасный стиль:

<?php
$app->addBackgroundJob(
    [OrderPing::class, 'run'],
    [15420] // попадет в run(int $orderId)
);

Приоритеты и jobs из jobs

Очередь внутри запроса приоритетная: сначала выше priority (NORMAL=100 выше LOW=50). Системные «не срочные» вещи вроде CAgent::CheckAgents в примере API ставят в LOW, чтобы ваши NORMAL-job успели раньше в том же хвосте.

Ядро обрабатывает очередь циклом: jobs могут добавлять новые jobs, и они тоже будут выполнены, пока очередь не опустеет. Это удобно для цепочки «после ответа: шаг A → поставил шаг B». Это же опасно: легко устроить длинный хвост и упереться в max_execution_time уже после того, как пользователь получил 200 OK. Пользователь счастлив, FPM-воркер ещё занят — и при аварии часть цепочки молча пропадает.

Правило: один job — одна короткая ответственность. Длинную цепочку выносите в агента или Messenger.

Исключения внутри job ядро логирует через exception handler; поведение «упал один — остальные» зависит от версии/ветки, поэтому не стройте бизнес-инварианты вида «job 2 выполнится всегда, даже если job 1 кинул Throwable». Проверяйте на своём main.

Типичные ловушки

Симптом Вероятная причина Что сделать
Job «иногда не бывает» Падение/kill PHP после ответа; это не durable queue Критичное — в агент/Messenger/свою очередь
set в сессии из job «молчит» Сессия закрыта до runBackgroundJobs Аргументы + БД/кеш до постановки job
Замыкание не видит свежие данные Захватили значение use до изменения Передавать id в $args, читать в job заново
FPM «висит» после 200 OK Слишком тяжёлый или цепочка jobs Укоротить; тяжёлое — в агент/очередь
Путаете с cron «раз в N минут» Неверный инструмент CAgent / cron
Статический вызов Application::addBackgroundJob(...) Метод нестатический Только getInstance()->addBackgroundJob(...)
Ждёте ретраев как в Rabbit Их нет в BackgroundJob Messenger retry_strategy (alpha) или свой брокер
Передали ассоциативный $args в метод с int $id Разъезд сигнатуры call_user_func_array Список позиционных аргументов или один payload-массив

Чеклист перед коммитом

  1. Job короткий (сотни миллисекунд, не минуты).
  2. Потеря одного выполнения допустима для бизнеса (или есть идемпотентный повтор другим механизмом).
  3. Данные переданы через $args / диск / БД, не через открытую сессию.
  4. Нет статического вызова Application::addBackgroundJob.
  5. Приоритет осознан: NORMAL для вашего хвоста, LOW для системного «можно позже в этом же запросе».
  6. Не подменили агент/cron BackgroundJob-ом.
  7. Для гарантий и ретраев запланирован Messenger (с оговоркой alpha) или внешняя очередь — не «надежда на хвост FPM».

Источники

Практические заметки и услуги по Битрикс — на karoche.ru.