Перейти к содержанию

Агенты

Отработка агентов

Битрикс делит агенты на периодические (IS_PERIOD = 'Y') и непериодические (IS_PERIOD='N').

Проверка агентов происходит в прологе www/bitrix/modules/main/include.php:153. Проверяется значение check_agents. Если её не задавать, либо установить значение 'N' - агенты перестанут вызываться в прологе.

Крон для выполнения агентов запускает файл cron_events.php, на хитах этот файл не читается. Установкой констант в dbconn.php, и в cron_events.php можно контролировать, агенты какого типа будут выполняться на хитах или на кроне. Главное помнить, что определить константу можно лишь единожды.

Агенты или хиты

Пролог подключается всегда, и на хитах, и на кроне. Какого-то определённого метода, который однозначно определяет, запущен код на хите или на кроне — нет. Логика строится на том, что в файле cron_events.php до подключения пролога определяется набор переменных, с помощью которых контролируется запуск агентов.

Если надо все агенты перенести на cron, то можно отключить вызов агентов на прологе, а в cron_events.php явно вызывать CAgent::CheckAgents().

Если же надо чтоб некоторые агенты выполнялись на хитах, тогда часть констант определяется в dbconn.php, часть в cron_events.php, часть в b_option. И надо подумать как правильно вызывать CAgent::CheckAgents(). Учитывая что он будет срабатывать в прологе, то надо ли его запускать ещё раз в cron_events.php?

Логика выборки и запуска

Лимит запуска проверяется в методе CAgent::ExecuteAgents:

class CAgent
{
    public static function ExecuteAgents()
    {
        // Three states: no cron (null), on cron (true), on hit (false). Как мне кажется, коментарий только запутывает
        $cron = static::OnCron();

        // дополнительный фильтр по типу агента
        if ($cron !== null) {
            $str_crontab = ($cron ? " AND IS_PERIOD='N' " : " AND IS_PERIOD='Y' ");
        } else {
            $str_crontab = '';
        }

        // определение лимита на выборку
        $limit = $cron ? COption::GetOptionInt("main", "agents_cron_limit", 1000) : COption::GetOptionInt("main", "agents_limit", 100);
    }

    protected static function OnCron()
    {
        if (COption::GetOptionString('main', 'agents_use_crontab', 'N') == 'Y' || (defined('BX_CRONTAB_SUPPORT') && BX_CRONTAB_SUPPORT === true)) {
            return (defined('BX_CRONTAB') && BX_CRONTAB === true);
        }
        return null;
    }
}

  • Определяется переменная $cron (назовём её условно Тип запуска агентов). Меняя результат этой переменной можно контролировать, агенты какого типа будут получены в данный момент:
    $cron = static::OnCron();
    
  • Формируется дополнительное sql условие для фильтра по видам агентов в зависимости от комбинации параметров и констант:
    -- либо не добавляется: $cron === null
    -- только непериодические: $cron === true
    AND IS_PERIOD='N'
    -- только периодические: $cron === false
    AND IS_PERIOD='Y'
    
  • Собирается sql для выборки агентов.
    SELECT ID, NAME, AGENT_INTERVAL, IS_PERIOD, MODULE_ID, RETRY_COUNT
    FROM b_agent
    WHERE ACTIVE = 'Y'
      AND NEXT_EXEC <= NOW()
      AND (DATE_CHECK IS NULL OR DATE_CHECK <= NOW())
      -- сюда прописывается доп условие из предыдущего пункта
    ORDER BY RUNNING ASC, SORT desc;
    
  • Формируется LIMIT:
  • $cron === true - лимит берётся из agents_cron_limit
  • иначе лимит берётся из agents_limit

Перевод всех агентов на крон

  • $cron = static::OnCron() должен вернуть null (CAllAgent::OnCron()). Для этого:
    COption::SetOptionString('main', 'agents_use_crontab', 'N');
    const BX_CRONTAB_SUPPORT = false;
    
  • Так как $cron === null, то лимиты берутся из agents_limit:
    COption::SetOptionInt('main', 'agents_limit', 500);
    

Отключение агентов на хитах

Так как все агенты перевели на крон, то нужно исключить запуск агентов на хитах.

COption::SetOptionString('main', 'check_events', 'N');

Некоторые константы

  • BX_CRONTAB в случае если типы агентов разделены между хитами и кроном, с помощью этой переменной можно контролировать, какой тип агентов на чём запускается
  • BX_CRONTAB_SUPPORT участвует в логике определения, нужно ли разделять запуск агентов между хитами и кроном
  • 'check_events' — отключение запуска агентов в прологе

Первый запуск

При первичном чтении агента выставляется значение DATE_CHECK - дата следующей проверки

UPDATE b_agent
SET DATE_CHECK = DATE_ADD(now(), INTERVAL 600 SECOND)
WHERE ID IN (29)

Перед запуском метода проставляется флаг запуска, инкрементируется флаг повтора

UPDATE b_agent
SET
    RUNNING = 'Y',
    RETRY_COUNT = RETRY_COUNT + 1
WHERE ID = 29

Второй запуск

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

{
  "ID": 29,
  "NAME": "debugSleep();",
  "ACTIVE": "Y",
  "LAST_EXEC": null,
  "NEXT_EXEC": "2022-08-18 08:56:00",
  "DATE_CHECK": "2022-08-18 09:31:30",
  "RUNNING": "Y",
  "RETRY_COUNT": 1
}

Идёт попытка получить агентов

SELECT 'x'
FROM b_agent
WHERE ACTIVE = 'Y'
    AND NEXT_EXEC <= now()
    AND (DATE_CHECK IS NULL OR DATE_CHECK <= now())
LIMIT 1
Так как на предыдущем шаге был задан интервал 600 секунд (10 мин), то долгий агент не попадёт сюда

Ещё запуск с подходящим DATE_CHECK

Состояние агента примерно такое

{
  "ID": 29,
  "NAME": "debugSleep();",
  "ACTIVE": "Y",
  "LAST_EXEC": null,
  "NEXT_EXEC": "2022-08-18 06:56:00",
  "DATE_CHECK": "Устраивающее проверку время (600 секунд)",
  "RUNNING": "Y",
  "RETRY_COUNT": 1
}
Снова получаются агенты. Странно что не проверяется флаг запущенности агента (RUNNING). Баг?
SELECT ID, NAME, AGENT_INTERVAL, IS_PERIOD, MODULE_ID, RETRY_COUNT
FROM b_agent
WHERE ACTIVE = 'Y'
  AND NEXT_EXEC <= now()
  AND (DATE_CHECK IS NULL OR DATE_CHECK <= now())
ORDER BY RUNNING ASC, SORT desc
Проверяется значение параметра количества запусков
UPDATE b_agent SET ACTIVE='N' WHERE ID = 29
if ($arAgent['RETRY_COUNT'] >= 3) {
    $DB->Query("UPDATE b_agent SET ACTIVE='N' WHERE ID = ".$arAgent["ID"]);
    continue; // выход из логики запуска агента
}

Если попытки не исчерпаны - идём дальше.

Повторяется запрос на обновление RUNNING и RETRY_COUNT.

Запускается функция агента. Если функция ничего не вернула - агент удаляется

DELETE FROM b_agent WHERE ID = 29

Иначе агент обновляется. Флаг ACTIVE при этом не трогается

UPDATE b_agent
SET
  NAME = 'debugSleep();',
  LAST_EXEC = now(),
  NEXT_EXEC = DATE_ADD(now(), INTERVAL 84000 SECOND),
  DATE_CHECK = NULL,
  RUNNING = 'N',
  RETRY_COUNT = 0
WHERE ID = 29

Проблемные моменты

Получается, битрикс не проверяет факт того что агент, запущенный ранее, до сих пор работает. Методы, рыботающие больше 600 секунд, запустятся по-новой с увеличением значения RETRY_COUNT. И в очередной раз при превышении этого показателя битрикс просто деактивирует такого агента.

При этом агент успешно завершится, обновятся поля следующего запуска, но он останется неактивным.