Агенты
Отработка агентов
Битрикс делит агенты на периодические (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(назовём её условноТип запуска агентов). Меняя результат этой переменной можно контролировать, агенты какого типа будут получены в данный момент: - Формируется дополнительное sql условие для фильтра по видам агентов в зависимости от комбинации параметров и констант:
- Собирается sql для выборки агентов.
- Формируется LIMIT:
$cron === true- лимит берётся изagents_cron_limit- иначе лимит берётся из
agents_limit
Перевод всех агентов на крон
$cron = static::OnCron()должен вернутьnull(CAllAgent::OnCron()). Для этого:- Так как
$cron === null, то лимиты берутся изagents_limit:
Отключение агентов на хитах
Так как все агенты перевели на крон, то нужно исключить запуск агентов на хитах.
Некоторые константы
BX_CRONTABв случае если типы агентов разделены между хитами и кроном, с помощью этой переменной можно контролировать, какой тип агентов на чём запускаетсяBX_CRONTAB_SUPPORTучаствует в логике определения, нужно ли разделять запуск агентов между хитами и кроном'check_events'— отключение запуска агентов в прологе
Первый запуск
При первичном чтении агента выставляется значение DATE_CHECK - дата следующей проверки
Перед запуском метода проставляется флаг запуска, инкрементируется флаг повтора
Второй запуск
На момент второго запуска при условии что метод работает очень долго, агент будет выглядеть примерно так:
{
"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
Ещё запуск с подходящим 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
if ($arAgent['RETRY_COUNT'] >= 3) {
$DB->Query("UPDATE b_agent SET ACTIVE='N' WHERE ID = ".$arAgent["ID"]);
continue; // выход из логики запуска агента
}
Если попытки не исчерпаны - идём дальше.
Повторяется запрос на обновление RUNNING и RETRY_COUNT.
Запускается функция агента. Если функция ничего не вернула - агент удаляется
Иначе агент обновляется. Флаг 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.
И в очередной раз при превышении этого показателя битрикс просто деактивирует такого агента.
При этом агент успешно завершится, обновятся поля следующего запуска, но он останется неактивным.