Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с ситуацией, когда код, идеально работающий в интерактивном режиме (например, в составе внешней обработки), начинает вести себя непредсказуемо при переносе его в фоновое или регламентное задание. Особенно коварны случаи, когда процесс завершается «молча»: система не выдает никаких видимых ошибок в блоке Попытка...Исключение, а фоновое задание просто прерывается или начинает выполняться заново.
В этой статье мы подробно разберем именно такую проблему. Рассмотрим, почему при вызове метода Выборка.Получить() или ПолучитьОбъект() внутри фонового задания происходит тихий сбой, почему стандартный перехватчик ошибок не спасает ситуацию, и как пошагово диагностировать и устранить эту уязвимость в кодовой базе.
Для начала проанализируем фрагмент кода, который вызывает сбой в фоновом задании:
ЗаписьXML.ОткрытьФайл(Файл);
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Узел);
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, Узел.НомерОтправленного, );
Пока Выборка.Следующий() Цикл
Попытка
Объект = Выборка.Получить();
ЗаписатьXML(ЗаписьXML, Объект);
Исключение
Текст = ОписаниеОшибки();
Продолжить;
КонецПопытки;
КонецЦикла;
На первый взгляд, этот код написан абсолютно корректно и безопасно. Разработчик предусмотрел конструкцию Попытка...Исключение вокруг метода получения объекта из базы данных. Логика подсказывает: если при чтении или записи какого-то конкретного объекта возникнет сбой, система запишет ошибку в переменную Текст и продолжит цикл для обработки остальных объектов.
Однако на практике в фоновом задании происходит следующее: как только выполнение доходит до строки Объект = Выборка.Получить();, работа процедуры мгновенно прекращается. Секция Исключение не срабатывает. Фоновое задание либо полностью завершается со статусом «Ошибка» (при этом в журнале регистрации может не быть вменяемого описания), либо, если настроен автоматический перезапуск, запускается заново, создавая бесконечный цикл.
Для того чтобы понять природу этого явления, нам необходимо погрузиться в особенности архитектуры компиляции платформы 1С:Предприятие. Здесь играют роль два ключевых фактора: «ленивая» компиляция и концепция невосстановимых ошибок.
1. «Ленивая» компиляция модулей объектов. Платформа 1С не компилирует программный код всех объектов метаданных (справочников, документов, бизнес-процессов) в момент старта сеанса или запуска сервера. Компиляция модуля конкретного объекта происходит динамически — ровно в тот момент, когда к нему происходит первое обращение в кодовой строке. Вызов метода Выборка.Получить() (при обходе выборки зарегистрированных изменений плана обмена) или метода ПолучитьОбъект() инициирует создание экземпляра объекта в оперативной памяти сервера и, как следствие, компиляцию его модуля.
2. Невосстановимые исключения компиляции. Если в процессе компиляции модуля объекта платформа обнаруживает критическую синтаксическую или контекстную ошибку (например, обращение к процедуре, которая недоступна в текущем контексте выполнения), это исключение классифицируется платформой как невосстановимое. Ошибки компиляции и инициализации модулей ломают сам контекст выполнения потока. Платформа физически не может продолжить выполнение текущего сеанса и аварийно завершает поток выполнения (или даже весь рабочий процесс rphost), полностью игнорируя конструкции Попытка...Исключение.
Именно поэтому фоновое задание «вылетает» мгновенно, не оставляя разработчику никаких шансов поймать ошибку стандартным обработчиком ОписаниеОшибки().
Поскольку фоновое задание всегда выполняется исключительно на стороне сервера 1С, модуль загружаемого объекта также компилируется в контексте «Сервер». Сбой происходит, если внутри модуля объекта (особенно в его нелокальной области — разделе инициализации в самом низу модуля, либо в предопределенных обработчиках событий вроде ПередЗаписью, ПриЗаписи, ПриКопировании) присутствует код, предназначенный исключительно для работы на клиенте.
Проанализируем наиболее частые ошибки написания кода в модулях объектов:
Предупреждение(), Вопрос(), ПоказатьПредупреждение()). На сервере нет графического интерфейса, и вызов этих методов приводит к моментальному падению процесса.Рассмотрим пример некорректного кода в модуле объекта (документа или справочника):
// Неправильный код в модуле объекта
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимОтменыПроведения)
Если ЭтоНовый() Тогда
// Ошибка! Метод Вопрос() недоступен на сервере!
Если Вопрос("Хотите заполнить реквизиты по умолчанию?", РежимДиалогаВопрос.ДаНет) = КодВозвратаДиалога.Да Тогда
ЗаполнитьРеквизитыПоУмолчанию();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При интерактивной работе пользователя в режиме обычного или тонкого клиента этот код будет успешно выполняться. Но как только фоновое задание попытается вызвать Выборка.Получить() для этого документа, произойдет аварийное завершение процесса, поскольку на сервере метод Вопрос() не существует.
Для предотвращения подобных сбоев весь код, завязанный на интерактивное взаимодействие с пользователем или использующий клиентские методы, должен быть надежно скрыт от компилятора сервера с помощью инструкций препроцессора #Если Клиент Тогда.
Перепишем наш пример правильно:
// Правильный (безопасный) код в модуле объекта
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимОтменыПроведения)
#Если Клиент Тогда
Если ЭтоНовый() Тогда
Если Вопрос("Хотите заполнить реквизиты по умолчанию?", РежимДиалогаВопрос.ДаНет) = КодВозвратаДиалога.Да Тогда
ЗаполнитьРеквизитыПоУмолчанию();
КонецЕсли;
КонецЕсли;
#КонецЕсли
КонецПроцедуры
Теперь при компиляции модуля на сервере (в контексте фонового задания) компилятор просто проигнорирует блок кода, находящийся внутри директивы #Если Клиент Тогда, и объект будет успешно прочитан из базы данных без возникновения критических ошибок.
Если фоновое задание уже падает, а вы не знаете, в каком именно объекте кроется ошибка, воспользуйтесь следующими проверенными методами диагностики.
Это самый быстрый и надежный способ выявить некорректный код еще на этапе разработки, не прибегая к запуску фоновых заданий. Для этого выполните следующие шаги:
Платформа выполнит компиляцию всех модулей для указанных контекстов и выведет список строк, в которых содержатся несовместимые серверные вызовы. Вам останется лишь перейти по ссылкам и обернуть проблемные участки в инструкции препроцессора.
Если стандартный Журнал регистрации пуст, а падение фонового задания приводит к краху рабочего процесса rphost, на помощь приходит Технологический журнал 1С. ТЖ перехватывает абсолютно все события на уровне платформы. Для локализации нашей проблемы достаточно настроить сбор событий EXCP (Exceptions/Исключения).
Создайте в каталоге настроек ТЖ файл logcfg.xml следующего содержания:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\v8_logs\exceptions" history="24">
<event>
<eq property="Name" value="EXCP"/>
</event>
<property name="all"/>
</log>
</config>
Запустите фоновое задание. После его очередного «тихого» падения откройте созданный лог-файл в каталоге C:\v8_logs\exceptions. В логе вы найдете подробную трассировку стека вызовов, которая укажет на конкретный модуль объекта и номер строки, вызвавшей аварийный останов процесса.
Чтобы зафиксировать ошибку непосредственно в момент её возникновения в режиме реального времени, выполните следующие действия:
Если ваша конфигурация разработана на базе БСП (Библиотеки стандартных подсистем), вы можете временно перевести выполнение длительной операции в синхронный (клиентский) режим. Это позволит выполнить тот же самый код в контексте вашего текущего сеанса без ухода в фоновый процесс, что значительно упростит пошаговый проход отладчиком.
Применяя эти несложные правила написания безопасного серверного кода и используя встроенные инструменты диагностики платформы, вы сможете быстро локализовать подобные «невидимые» ошибки и гарантировать стабильную работу фоновых и регламентных заданий вашей информационной системы.