В процессе разработки и администрирования систем на платформе «1С:Предприятие 8» программисты часто сталкиваются с загадочным поведением фоновых процессов. Регламентное задание запускается по расписанию, отрабатывает за доли секунды, в консоли заданий или журнале регистрации мы видим заветный статус «Выполнено успешно», однако фактического изменения данных в информационной базе не происходит. При этом, если запустить тот же самый код интерактивно (через кнопку на форме или внешнюю обработку), система отрабатывает корректно, перезаписывая все необходимые объекты.
В этой статье мы подробно разберем, почему возникает такая ситуация, проанализируем внутренние механизмы платформы 1С и рассмотрим пошаговое руководство по диагностике и устранению этой проблемы.
Для начала рассмотрим типичный пример кода, который часто пишется для подобных задач. Предположим, нам необходимо обойти справочник Номенклатура и удалить из наименований всех элементов техническую приставку "удалить".
Процедура ВыполнитьОбработку() Экспорт
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ
| СправочникНоменклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
|ГДЕ
| СправочникНоменклатура.Наименование ПОДОБНО ""%удалить%""";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если ЗначениеЗаполнено(Выборка.Ссылка) Тогда
ОбъектНоменклатура = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатура.Наименование = СтрЗаменить(ОбъектНоменклатура.Наименование, "удалить", "");
ОбъектНоменклатура.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
На первый взгляд код выглядит абсолютно рабочим. Однако в нем есть избыточные конструкции, а также скрытые логические ловушки:
РАЗЛИЧНЫЕ в данном запросе избыточно, так как поле Ссылка справочника по определению уникально.ЗначениеЗаполнено(Выборка.Ссылка) внутри цикла обхода результата запроса не имеет практического смысла. Запрос к реальной таблице справочника не может вернуть пустую или битую ссылку в выборке (если только не используются левые соединения с другими таблицами).Если этот код запускается вручную под учетной записью администратора, он отрабатывает без нареканий. Но при старте в фоновом режиме регламентного задания изменений не происходит. Почему так получается? Разберем ключевые причины по порядку.
Это самая распространенная причина "тихого" невыполнения кода. Регламентные задания 1С всегда выполняются исключительно на сервере в контексте фонового сеанса. При ручном запуске обработки код чаще всего стартует в контексте клиента (в толстом или тонком клиенте), после чего управление может передаваться на сервер.
При вызове метода ОбъектНоменклатура.Записать() платформа последовательно запускает транзакционные события модуля объекта: ПередЗаписью и ПриЗаписи. Также срабатывают подписки на события, зарегистрированные в конфигурации для данного типа объекта.
Проанализируем следующую ситуацию: в обработчике ПередЗаписью или ПриЗаписи модуля объекта справочника Номенклатура разработчик (или типовой механизм конфигурации) вызывает процедуру из общего модуля. Если у этого общего модуля в свойствах не установлена галочка "Сервер" (а установлена, например, только "Клиент (управляемое приложение)" или "Толстый клиент"), серверный процесс фонового задания просто не сможет скомпилировать этот участок кода.
В некоторых случаях это приводит к падению фонового задания с ошибкой, но если вызов обернут в неявную конструкцию Попытка ... Исключение внутри типовых библиотек, ошибка будет "проглочена". Транзакция записи отменится, изменения не сохранятся, а само регламентное задание завершится со статусом успешного выполнения.
Решение: Проверить все подписки на события записываемого объекта и процедуры модуля объекта ПередЗаписью / ПриЗаписи. Убедиться, что все вызываемые в них общие модули имеют признак выполнения на сервере.
При интерактивном запуске код выполняется под правами текущего пользователя (обычно администратора с полными правами). Регламентное задание выполняется в фоновом сеансе. В свойствах регламентного задания в конфигураторе или в пользовательском интерфейсе можно указать конкретного пользователя, под именем которого оно должно стартовать.
Если пользователь для регламентного задания не задан, или у назначенного пользователя ограничены права (например, настроен механизм RLS — Restriction on Level of Records), поведение системы резко меняется:
Номенклатура по определенным условиям (например, по группам доступа или организациям), запрос вернет 0 строк.Пока Выборка.Следующий() даже не начнется. Задание завершится мгновенно и абсолютно успешно, ведь никаких ошибок в коде не было, просто не нашлось данных для обработки.Решение: Для того чтобы регламентное задание гарантированно имело доступ ко всем данным информационной базы в обход ограничений RLS, необходимо в самом начале процедуры принудительно включить привилегированный режим.
Рассмотрим, как правильно доработать код:
Процедура ВыполнитьОбработку() Экспорт
// Включаем привилегированный режим для обхода RLS и проверок прав
УстановитьПривилегированныйРежим(Истина);
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| СправочникНоменклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК СправочникНоменклатура
|ГДЕ
| СправочникНоменклатура.Наименование ПОДОБНО ""%удалить%""";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбъектНоменклатура = Выборка.Ссылка.ПолучитьОбъект();
ОбъектНоменклатура.Наименование = СтрЗаменить(ОбъектНоменклатура.Наименование, "удалить", "");
Попытка
ОбъектНоменклатура.Записать();
Исключение
// Обязательно фиксируем возможные ошибки записи в Журнал регистрации
ЗаписьЖурналаРегистрации("РегламентныеЗадания.ОшибкаЗаписи",
УровеньЖурналаРегистрации.Ошибка,
ОбъектНоменклатура.Метаданные(),
ОбъектНоменклатура.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Часто в логику объектов (модули справочников, документов) встраивают вызовы функций, которые требуют взаимодействия с пользователем. Например:
ПоказатьПредупреждение(), Вопрос().Если код записи объекта доходит до выполнения таких интерактивных инструкций на сервере (в контексте фонового задания), платформа сгенерирует исключение, так как на сервере нет и не может быть окон диалога. Если это исключение перехвачено блоком Попытка...Исключение, запись будет тихо отменена, а регламентное задание отчитается об успешном завершении.
Решение: Обернуть все интерактивные вызовы в директивы компиляции или условия проверки контекста выполнения. Например, использовать проверку:
#Если Клиент Тогда или проверять системные параметры выполнения, чтобы не вызывать диалоговые окна при фоновой работе системы.
Если причину выявить не удалось, проведем пошаговую диагностику с помощью технологических инструментов платформы.
Добавим в код контрольные точки фиксации событий (логирование). Это позволит точно узнать, заходит ли выполнение в цикл, сколько элементов выбирает запрос и на каком этапе прерывается алгоритм.
ЗаписьЖурналаРегистрации("РегламентноеЗадание.Старт", УровеньЖурналаРегистрации.Информация,,, "Запуск обработки.");
Выборка = Запрос.Выполнить().Выбрать();
ЗаписьЖурналаРегистрации("РегламентноеЗадание.Запрос", УровеньЖурналаРегистрации.Информация,,, "Запрос выполнен. Количество записей: " + Выборка.Количество());
Пока Выборка.Следующий() Цикл
ЗаписьЖурналаРегистрации("РегламентноеЗадание.Цикл", УровеньЖурналаРегистрации.Информация,,, "Обработка ссылки: " + Выборка.Ссылка);
// Код записи объекта...
КонецЦикла;
ЗаписьЖурналаРегистрации("РегламентноеЗадание.Финиш", УровеньЖурналаРегистрации.Информация,,, "Обработка завершена.");
После выполнения задания откроем Журнал регистрации в режиме 1С:Предприятие и проанализируем созданные записи. Если в логах есть событие "Запрос" со значением "Количество записей: 0", значит, проблема в условиях запроса или ограничениях прав (RLS). Если логи обрываются или содержат ошибки внутри цикла — мы локализуем конкретный проблемный элемент номенклатуры.
Для того чтобы зайти отладчиком в код, выполняемый регламентным заданием на сервере, выполним следующие настройки:
ВыполнитьОбработку() и дождемся автоматического запуска регламентного задания по расписанию (или инициируем его запуск принудительно через консоль регламентных заданий).Используя этот подход, мы сможем детально отследить по шагам весь процесс записи объекта и обнаружить, в какой именно момент (например, при вызове подписки на событие или общего модуля) происходит сброс транзакции записи.