Cron/Agent: шаблон lock-файла для защиты от параллельного запуска
Шаблон агента или cron-задачи с файловой блокировкой (flock) для предотвращения параллельного выполнения на одной ноде.
Как использовать
- Скопируйте нужный фрагмент кода.
- Вставьте в свой проект и при необходимости измените под задачу.
- Проверьте зависимости и окружение (версии, переменные).
Шаблон агента или cron-задачи с файловой блокировкой через flock() для защиты от параллельного запуска. Используйте для долгих задач, импортов, синхронизаций, которые не должны выполняться одновременно.
use Bitrix\Main\Application;
/**
* Шаблон агента с файловой блокировкой
* @return string Строка для повторного запуска агента
*/
function MyLongRunningAgent() {
$lockFile = $_SERVER['DOCUMENT_ROOT'] . '/upload/locks/my_agent.lock';
$lockHandle = @fopen($lockFile, 'c+'); // c+ - создаёт файл если нет
if (!$lockHandle) {
// Не удалось открыть файл, пропускаем выполнение
return "MyLongRunningAgent();";
}
// Пытаемся получить эксклюзивную блокировку (non-blocking)
// LOCK_EX - эксклюзивная блокировка
// LOCK_NB - non-blocking (не ждём, если файл заблокирован)
if (!flock($lockHandle, LOCK_EX | LOCK_NB)) {
// Файл уже заблокирован другим процессом
fclose($lockHandle);
return "MyLongRunningAgent();"; // Пропускаем выполнение
}
// Записываем PID процесса в lock-файл (опционально, для отладки)
ftruncate($lockHandle, 0);
fwrite($lockHandle, getmypid());
fflush($lockHandle);
try {
// ===== ВАША ЛОГИКА АГЕНТА =====
// Пример: обработка элементов
$processed = 0;
$limit = 100;
// Ваш код здесь
// ...
// ===== КОНЕЦ ЛОГИКИ =====
} catch (\Exception $e) {
// Логируем ошибку
if (class_exists('SafeFileLogger')) {
$logger = new SafeFileLogger('agents.log');
$logger->error('Agent error', [
'agent' => 'MyLongRunningAgent',
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
}
} finally {
// ВАЖНО: всегда освобождаем блокировку в finally
flock($lockHandle, LOCK_UN);
fclose($lockHandle);
// Опционально: удаляем lock-файл
@unlink($lockFile);
}
// Возвращаем строку для повторного запуска
return "MyLongRunningAgent();";
}
/**
* Альтернатива: проверка через время последнего запуска
*/
function MyAgentWithTimeCheck() {
$lockFile = $_SERVER['DOCUMENT_ROOT'] . '/upload/locks/my_agent_time.lock';
$lockTimeout = 300; // 5 минут
// Проверяем время последнего запуска
if (file_exists($lockFile)) {
$lastRun = filemtime($lockFile);
if (time() - $lastRun < $lockTimeout) {
// Ещё не прошло достаточно времени или выполняется
return "MyAgentWithTimeCheck();";
}
}
// Создаём/обновляем lock-файл
touch($lockFile);
try {
// Ваша логика
// ...
} finally {
// Обновляем время последнего запуска
touch($lockFile);
}
return "MyAgentWithTimeCheck();";
}
Usage:
// Регистрация агента
CAgent::AddAgent(
"MyLongRunningAgent();",
"",
"N",
3600, // Интервал в секундах
"",
"Y",
"",
100
);
// Или через админку: Настройки -> Настройки продукта -> Агенты
Notes:
⚠️ flock() работает только на одной ноде сервера. Для кластеров используйте блокировку через БД (GET_LOCK в MySQL). Всегда освобождайте блокировку в finally блоке, чтобы она снялась даже при ошибке. Lock-файлы должны быть в /upload/locks/ с правами на запись. PID в lock-файле помогает отлаживать зависшие процессы.