#Агенты

Советы с тегом "Агенты"

1 совет

Как защитить агент импорта от двойного запуска с помощью Connection::lock()

Как защитить агент импорта от двойного запуска с помощью Connection::lock()

Проблема/контекст

Для агентов, cron-задач и CLI-команд в Битрикс типовая ошибка одна: процесс стартует второй раз, пока первая копия еще держит критическую секцию. В результате появляются дубли импорта, повторные списания, гонки при обновлении сущностей и нестабильные ошибки, которые сложно воспроизвести.

В ядре для этого уже есть встроенный механизм. В Bitrix\Main\DB\MysqlCommonConnection::lock() платформа переводит пул в master only, вызывает SELECT GET_LOCK(...), а имя блокировки нормализует через CMain::GetServerUniqID(). Это важная деталь: блокировка берется не на уровне PHP-процесса, а на уровне текущего соединения с основной БД, поэтому она подходит именно для межпроцессной синхронизации.

Решение с кодом

Если задача должна выполняться строго в одном экземпляре, ставьте блокировку в самом начале и всегда снимайте ее в finally. Такой подход соответствует и самому ядру: Bitrix\Main\Messenger\Internals\Storage\Db\DbStorage не читает очередь без Application::getConnection()->lock('queueLock'), а в main/classes/general/usertype.php изменение UTS-таблиц обернуто в блокировку uf_add_*.

        <?php
declare(strict_types=1);

use Bitrix\Main\Application;
use Bitrix\Main\Diag\Logger;
use Bitrix\Main\Loader;

final class CatalogImportAgent
{
    public static function run(): string
    {
        $connection = Application::getConnection();
        $lockName = 'catalog_import';

        // Вторая копия не ждет: если импорт уже идет, просто выходим
        if (!$connection->lock($lockName, 0))
        {
            return CatalogImportAgent::class . '::run();';
        }

        try
        {
            Loader::includeModule('iblock');
            (new ImportService())->sync();
        }
        catch (\Throwable $e)
        {
            Logger::create('catalog_import')?->error($e->getMessage());
        }
        finally
        {
            // Освобождаем lock даже при исключении
            $connection->unlock($lockName);
        }

        return CatalogImportAgent::class . '::run();';
    }
}

    

Практический вывод здесь двойной. Во-первых, named lock не заменяет транзакцию: он защищает вход в критическую секцию, а не целостность отдельных SQL-операций. Во-вторых, имя должно быть стабильным и описывать конкретный ресурс, например catalog_import, prices_sync или agent_invoice_export. Если использовать случайные ключи, механизм потеряет смысл. Для долгих задач имеет смысл выбирать ненулевой таймаут, но для агентов и cron-скриптов чаще полезнее мгновенно завершить повторный запуск и дождаться следующего окна.

Итог

Connection::lock() в Битрикс уже решает задачу одиночного запуска без самодельных файлов-флажков и временных таблиц. Если процесс нельзя выполнять параллельно, ставьте named lock до начала работы и снимайте его гарантированно через finally.

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.

AI Домовой История

0 / 100

Привет! Я помогу с вопросами по 1С-Битрикс.

Спрашивай про D7, ORM, компоненты или события.

Требуется авторизация

Войдите или зарегистрируйтесь, чтобы задавать вопросы AI-ассистенту.

Войти