framework_eng/skills/bsl-practices/background-jobs/SKILL.md
Use for designing, diagnosing, and fixing 1C background and scheduled jobs. Helps ensure idempotency, retry policy, checkpointing, mutexes, and separation of retryable/permanent errors.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework background-jobsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Key principle: A background job can be interrupted, restarted, or run again at any moment. The job code must tolerate this without data loss or duplicate work.
| Trigger | Action |
|---------|----------|
| A scheduled or background job is being designed | Define the contract: parameters, user, transaction, idempotency, locking, timeout |
| A job hangs, does not finish, duplicates work | Diagnose via the event log: find the first failure, check active background jobs and stale locks |
| A job error requires retry logic | Separate retryable and permanent errors, implement backoff |
| A job processes a large volume of data | Apply checkpointing and batch processing with intermediate commits |
| Multiple instances may run in parallel | Implement a mutex via БлокировкаДанных or a flag constant |
Context: You need to create a scheduled job that can be safely restarted and does not duplicate work.
Steps:
// Канонический паттерн идемпотентного захвата задачи
Функция ЗахватитьЗадачуДляОбработки(ЗадачаСсылка) Экспорт
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СостоянияЗадач");
ЭлементБлокировки.УстановитьЗначение("Задача", ЗадачаСсылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Читаем актуальный статус после блокировки
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СостоянияЗадач.Статус КАК Статус,
| СостоянияЗадач.НачалоОбработки КАК НачалоОбработки
|ИЗ
| РегистрСведений.СостоянияЗадач КАК СостоянияЗадач
|ГДЕ
| СостоянияЗадач.Задача = &Задача";
Запрос.УстановитьПараметр("Задача", ЗадачаСсылка);
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
// Уже обработано — пропускаем
Если Выборка.Статус = Перечисления.СтатусыЗадач.Обработано Тогда
ОтменитьТранзакцию();
Возврат Ложь;
КонецЕсли;
// Кто-то уже взял задачу (и не завис) — пропускаем
Если Выборка.Статус = Перечисления.СтатусыЗадач.ВОбработке
И (ТекущаяДата() - Выборка.НачалоОбработки) < 3600 Тогда
ОтменитьТранзакцию();
Возврат Ложь;
КонецЕсли;
КонецЕсли;
// Захватываем задачу
НаборЗаписей = РегистрыСведений.СостоянияЗадач.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Задача.Установить(ЗадачаСсылка);
Запись = НаборЗаписей.Добавить();
Запись.Задача = ЗадачаСсылка;
Запись.Статус = Перечисления.СтатусыЗадач.ВОбработке;
Запись.НачалоОбработки = ТекущаяДата();
НаборЗаписей.Записать();
ЗафиксироватьТранзакцию();
Возврат Истина;
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ФоновоеЗадание.ЗахватЗадачи'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецФункции
Context: A scheduled job must not run in two instances at the same time.
Steps:
Процедура ВыполнитьРегламентноеЗадание() Экспорт
// Попытка получить эксклюзивный лок
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Константа.ФлагЗапускаЗадания");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Попытка
Блокировка.Заблокировать();
Исключение
// Другой экземпляр уже работает — нормальная ситуация
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'РегламентноеЗадание.ИмяЗадания'"),
УровеньЖурналаРегистрации.Предупреждение,,,
НСтр("ru = 'Пропущен запуск: задание уже выполняется.'"));
Возврат;
КонецПопытки;
// Основная логика задания — выполняется только в одном экземпляре
ВыполнитьОсновнуюЛогику();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'РегламентноеЗадание.ИмяЗадания'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Context: The job processes thousands of objects. You need to save progress so that a restart does not begin from zero.
Key rules:
Процедура ОбработатьОбъектыСCheckpoint(РазмерБатча = 100) Экспорт
// Читаем checkpoint (откуда продолжать)
НачальнаяПозиция = ПолучитьCheckpoint();
МассивОбъектов = ПолучитьОбъектыДляОбработки(НачальнаяПозиция, РазмерБатча);
Пока МассивОбъектов.Количество() > 0 Цикл
НачатьТранзакцию();
Попытка
Для Каждого Объект Из МассивОбъектов Цикл
ОбработатьОдинОбъект(Объект);
КонецЦикла;
// Checkpoint и данные фиксируются атомарно
СохранитьCheckpoint(МассивОбъектов[МассивОбъектов.ВГраница()]);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ФоновоеЗадание.ПакетнаяОбработка'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
// Следующий батч
НачальнаяПозиция = ПолучитьCheckpoint();
МассивОбъектов = ПолучитьОбъектыДляОбработки(НачальнаяПозиция, РазмерБатча);
КонецЦикла;
КонецПроцедуры
Context: The job calls an external service or works with resources that may be temporarily unavailable.
Error classification:
| Type | Examples | Action |
|-----|---------|----------|
| Retryable (temporary) | Network timeout, service unavailable (503), lock acquisition | Retry with backoff, log Warning |
| Permanent | Invalid data, business rule violation, 404/400 | Do not retry, log Error, move the task to status Rejected |
Функция ВыполнитьСRetry(ПараметрыЗадачи) Экспорт
МаксПопыток = 3;
ЗадержкаСекунд = 30; // для ФоновогоЗадания — через повторный запуск планировщиком
Для НомерПопытки = 1 По МаксПопыток Цикл
Попытка
Результат = ВызватьВнешнийСервис(ПараметрыЗадачи);
ЗафиксироватьУспех(ПараметрыЗадачи, Результат);
Возврат Истина;
Исключение
ИнфОшибки = ИнформацияОбОшибке();
Если ЭтоPermanentОшибка(ИнфОшибки) Тогда
// Повтор бессмысленен
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ФоновоеЗадание.ВнешнийСервис'"),
УровеньЖурналаРегистрации.Ошибка,,,
СтрШаблон(НСтр("ru = 'Постоянная ошибка (повтор не поможет). %1'"),
ПодробноеПредставлениеОшибки(ИнфОшибки)));
ЗафиксироватьОтклонение(ПараметрыЗадачи, КраткоеПредставлениеОшибки(ИнфОшибки));
Возврат Ложь;
КонецЕсли;
// Retryable — логируем как предупреждение и продолжаем
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ФоновоеЗадание.ВнешнийСервис'"),
?(НомерПопытки < МаксПопыток,
УровеньЖурналаРегистрации.Предупреждение,
УровеньЖурналаРегистрации.Ошибка),,,
СтрШаблон(НСтр("ru = 'Попытка %1/%2. %3'"),
НомерПопытки, МаксПопыток,
ПодробноеПредставлениеОшибки(ИнфОшибки)));
Если НомерПопытки = МаксПопыток Тогда
ВызватьИсключение;
КонецЕсли;
КонецПопытки;
КонецЦикла;
Возврат Ложь;
КонецФункции
Функция ЭтоPermanentОшибка(ИнфОшибки)
ТекстОшибки = КраткоеПредставлениеОшибки(ИнфОшибки);
// Признак permanent: HTTP 4xx, бизнес-ошибки, невалидные данные
Возврат СтрНайти(ТекстОшибки, "400") > 0
ИЛИ СтрНайти(ТекстОшибки, "404") > 0
ИЛИ СтрНайти(ТекстОшибки, "422") > 0;
КонецФункции
Manually starting the job (for debugging and testing):
# Запустить конкретное регламентное задание через v8-runner
v8 run --ib <путь_к_ИБ> --execute "РегламентныеЗаданияСервер.ВыполнитьЗадание(<ИмяЗадания>)"
Diagnosing stuck jobs via the event log (event-log-analysis):
# Посмотреть ошибки фоновых заданий за последние 2 часа
v8 run --ib <путь_к_ИБ> --event-log --filter "ФоновоеЗадание" --level Error --hours 2
Checking active background jobs in the event log:
Look for events named background job. A stuck job is a Start event without a paired Finish and without Error - this is a candidate for a stale lock.
| Anti-pattern | Consequence | |-------------|-------------| | HTTP/external call inside a transaction | 30-second timeout = 30-second lock on the entire information base | | One transaction for the entire volume | Restart = rollback of all work | | No idempotency | Duplicate data on rerun | | Silent swallowing of errors | Data is lost, there is no trace in the event log | | Infinite retry without a limit | The job will block the queue forever | | Stale lock without TTL | The job does not start after a crash, the lock is not released |
Each job must write to the event log at start and finish:
// Старт задания
ЗаписьЖурналаРегистрации(
НСтр("ru = 'РегламентноеЗадание.<ИмяЗадания>.Старт'"),
УровеньЖурналаРегистрации.Информация,,,
СтрШаблон(НСтр("ru = 'Задание запущено. Параметры: %1'"),
<КраткоеОписаниеПараметров>));
// Финиш задания
ЗаписьЖурналаРегистрации(
НСтр("ru = 'РегламентноеЗадание.<ИмяЗадания>.Финиш'"),
УровеньЖурналаРегистрации.Информация,,,
СтрШаблон(НСтр("ru = 'Задание завершено. Обработано: %1, Ошибок: %2, Время: %3 сек'"),
КоличествоОбработано, КоличествоОшибок, Длительность));
БлокировкаДанных, canonical pattern НачатьТранзакцию/ЗафиксироватьТранзакцию/ОтменитьТранзакциюdepends_on:
testing
MUST use BEFORE making a judgment about the cause of a conflict, a test failure, or an artifact dispute. Defines the end-to-end verification method L1→L6 and the classification of the first broken link.
development
MUST use AFTER a work cycle with ≥2 iterations (wrote → error → fixed → success). Provides the retrospective procedure and the format for recording practice/anti-patterns in references/learned-patterns.md or {project}/.context/learned-patterns.md.
tools
MUST use WHEN you are writing reusable knowledge into RLM (pattern / architectural decision / stable domain fact) OR reading it before a non-trivial task/solution in the domain. Provides the breakdown of native-push vs RLM-pull, tools for writing and reading RLM, H-MEM levels, and hygiene.
testing
MUST use WHEN the task is classified as simple (< 20 lines, 1 file, no new metadata objects, no architectural decisions). Provides a short cycle of 3 steps with a guard on the self path and mandatory verify.