PHP
#bitrix#php#agent#cron#lock#idempotent#concurrency

Идемпотентный агент с блокировкой (lock file/DB)

Шаблон агента или cron-задачи с защитой от параллельного запуска через файловую блокировку или DB lock.

Как использовать

  1. Скопируйте нужный фрагмент кода.
  2. Вставьте в свой проект и при необходимости измените под задачу.
  3. Проверьте зависимости и окружение (версии, переменные).

Защищает агента от параллельного запуска через файловую блокировку или блокировку в БД. Используйте для долгих задач, которые не должны выполняться одновременно.

use Bitrix\Main\Application;
use Bitrix\Main\DB\Connection;

/**
 * Идемпотентный агент с файловой блокировкой
 */
function MyAgentTask() {
    $lockFile = $_SERVER['DOCUMENT_ROOT'] . '/upload/locks/my_agent.lock';
    $lockHandle = @fopen($lockFile, 'w');
    
    if (!$lockHandle) {
        return "MyAgentTask();"; // Не удалось создать lock, пропускаем
    }
    
    // Пытаемся получить эксклюзивную блокировку (non-blocking)
    if (!flock($lockHandle, LOCK_EX | LOCK_NB)) {
        fclose($lockHandle);
        return "MyAgentTask();"; // Уже выполняется, пропускаем
    }
    
    try {
        // Ваша логика агента
        // ...
        
        // Симулируем работу
        sleep(5);
        
    } finally {
        flock($lockHandle, LOCK_UN);
        fclose($lockHandle);
        @unlink($lockFile); // Опционально: удаляем lock файл
    }
    
    return "MyAgentTask();"; // Повторный запуск
}

/**
 * Альтернатива: блокировка через БД (для кластера)
 */
function MyAgentTaskWithDBLock() {
    $connection = Application::getConnection();
    $lockName = 'my_agent_lock';
    $lockTimeout = 300; // 5 минут
    
    // Пытаемся получить блокировку (MySQL GET_LOCK)
    $result = $connection->query("SELECT GET_LOCK('{$lockName}', {$lockTimeout}) as lock_acquired")->fetch();
    
    if (!$result || !$result['lock_acquired']) {
        return "MyAgentTaskWithDBLock();"; // Не удалось получить lock
    }
    
    try {
        // Ваша логика
        // ...
        
    } finally {
        // Освобождаем блокировку
        $connection->query("SELECT RELEASE_LOCK('{$lockName}')");
    }
    
    return "MyAgentTaskWithDBLock();";
}

Usage:

Зарегистрируйте агента через CAgent::AddAgent() или в админке. Lock файл создастся автоматически при первом запуске.

Notes:

⚠️ Для кластеров используйте DB lock (GET_LOCK). Файловый lock работает только на одной ноде. Убедитесь, что папка /upload/locks/ существует и доступна для записи.