Бесконечная рекурсия и циклы в иерархии: как найти виновника в 1С 8.3
Содержание
Зацикливание уровней в 1С 8.3 почти всегда означает одно из двух: код вызывает сам себя без выхода (рекурсия до переполнения стека) или в данных появился цикл — элемент справочника стал «родителем» собственного предка. Первое исправляют в коде конфигурации, второе — в данных базы. Чистка кэша помогает только тогда, когда ошибка осталась после исправления.
Отличить одно от другого можно за 10 минут: если ошибка возникает на конкретном элементе или документе — ищите цикл в данных. Если на любом объекте при одной и той же операции — ищите рекурсию в коде.
Как выглядит зацикливание и чем оно отличается от «зависания»
Зацикливание — это не медленная работа. Медленный отчёт рано или поздно закончится, а зацикленный код будет углубляться, пока не кончится память стека.
Типичные симптомы:
- 🔁 сообщение со словами «переполнение стека» или
Stack overflow; - 💥 клиент 1С закрывается без сообщения при записи документа или открытии формы;
- 🖥️ на сервере аварийно перезапускается рабочий процесс
rphost, у всех пользователей этого процесса обрываются сеансы; - ⏳ форма «думает» несколько секунд, память процесса растёт, затем — ошибка;
- 🌳 в иерархическом списке группа раскрывается сама в себя, или отчёт по иерархии не строится.
| Признак | Скорее всего | Где искать |
|---|---|---|
| Ошибка на одном элементе справочника | Цикл в иерархии данных | Поле «Родитель» элемента и его предков |
| Ошибка при записи любого документа вида | Рекурсия в обработчике записи | Модуль объекта, подписки на события |
| Ошибка после обновления или доработки | Новый код или расширение | Последние изменения конфигурации |
| Ошибка только у одного пользователя | Повреждённый кэш или настройки | Каталог кэша этого пользователя |
| Ошибка при построении отчёта СКД | Вычисляемые поля ссылаются друг на друга | Настройки схемы компоновки |
Диагностика зацикливания через журнал регистрации
Журнал регистрации — первое место, куда стоит смотреть. Он покажет, кто, когда и на каком объекте получил ошибку.
В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) журнал открывается из раздела Администрирование → Обслуживание. В Конфигураторе — через меню Администрирование → Журнал регистрации.
Порядок работы:
- Установите отбор по уровню важности — «Ошибка».
- Сузьте период до времени, когда пользователь получил сообщение.
- Отберите по пользователю, если ошибка воспроизводится у одного человека.
- Откройте запись и посмотрите поля «Данные» и «Метаданные» — там будет объект, на котором всё упало.
- В комментарии события ищите текст ошибки и имя модуля с номером строки.
Если в нескольких записях подряд фигурирует один и тот же элемент справочника, это сильный аргумент в пользу цикла в данных, а не в коде.
Журнал не всегда фиксирует падение. Когда процесс rphost аварийно завершается, запись об ошибке может не успеть попасть в журнал. Тогда нужен технологический журнал.
Технологический журнал для «тихих» падений
Технологический журнал включается файлом logcfg.xml в каталоге conf платформы (например, C:\Program Files\1cv8\conf\) на сервере 1С. Для ловли исключений достаточно собирать событие EXCP:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\1CLogs\excp" history="24">
<event>
<eq property="name" value="EXCP"/>
</event>
<property name="all"/>
</log>
</config>
Платформа подхватывает файл без перезапуска службы, в течение минуты. Воспроизведите ошибку, затем откройте логи в каталоге C:\1CLogs\excp и найдите событие с текстом про стек — рядом будет контекст вызова.
Технологический журнал быстро растёт. Включайте его на время диагностики и удаляйте logcfg.xml сразу после того, как поймали ошибку. Убедитесь, что на диске есть хотя бы несколько гигабайт свободного места.
Почему возникают рекурсивные вызовы
Рекурсия — нормальный приём, если у неё есть условие выхода. Зацикливание появляется, когда условие не срабатывает или его забыли.
Причины в коде
- ✍️ Запись объекта внутри его же обработчика записи. В
ПриЗаписивызываютЗаписать()для того же объекта — обработчик срабатывает снова, и так до переполнения. - 🔗 Подписка на событие, которая записывает «соседа». Подписка документа А записывает документ Б, а подписка Б записывает А.
- 🔄 Взаимный вызов функций. Функция общего модуля вызывает вторую, вторая при определённых данных — первую.
- 🧩 Расширение поверх типового кода. Перехват метода с аннотацией
&Вместо, который вызывает исходный метод черезПродолжитьВызов(), а тот снова приводит к перехваченному. - 🌲 Рекурсивный обход иерархии без защиты. Функция поднимается по полю «Родитель» и уходит в бесконечность, если в данных есть цикл.
Причины в данных
- 📂 Элемент справочника указан родителем для группы, которая сама его содержит. Обычно это результат загрузки данных из внешнего файла или обмена, где проверки отключены режимом
ОбменДанными.Загрузка. - 📋 Спецификация, в которой изделие входит в собственный состав через промежуточный полуфабрикат.
- 🧮 В отчёте СКД пользовательское вычисляемое поле ссылается на другое, которое ссылается обратно.
Отладка: как найти проблемное место в коде
Если журнал указал на модуль, следующий шаг — отладчик Конфигуратора. На рабочей базе отлаживать не стоит: делайте это на копии.
- Выгрузите копию базы и разверните её как отдельную информационную базу.
- Откройте копию в Конфигураторе, включите
Отладка → Остановка по ошибке. - Запустите клиент из Конфигуратора:
Отладка → Начать отладкуи выберите тип клиента. - Повторите действие, на котором возникает ошибка.
- Когда отладчик остановится, откройте окно
Отладка → Стек вызовов.
Стек вызовов — главный инструмент. При зацикливании в нём видна повторяющаяся пара или тройка процедур: ПриЗаписи → ОбработатьСвязанные → ПриЗаписи → ОбработатьСвязанные… Это и есть петля. Ищите, в каком месте цепочки должен был сработать выход.
Для отладки кода на сервере в клиент-серверном варианте сервер 1С должен быть запущен с разрешённой отладкой. На рабочем сервере это включают только по согласованию — отладка нагружает сервер.
Если ошибка «плавающая», поставьте точку останова с условием. Например, по счётчику: остановиться, когда процедура вызвана в сотый раз. Нормальный код столько вложенных вызовов не делает.
Проверка данных на цикл
Цикл, где элемент — родитель самого себя, находится одним запросом. Запустите его в консоли запросов на копии базы:
ВЫБРАТЬ
Справочник.Ссылка,
Справочник.Наименование
ИЗ
Справочник.Номенклатура КАК Справочник
ГДЕ
Справочник.Родитель = Справочник.Ссылка
Длинный цикл (А → Б → В → А) одним запросом не поймать. Его проще найти обходом с запоминанием пройденных элементов:
Функция НайтиЦиклВИерархии(Элемент) Экспорт
Пройденные = Новый Соответствие;
Текущий = Элемент;
Пока ЗначениеЗаполнено(Текущий) Цикл
Если Пройденные[Текущий] <> Неопределено Тогда
Возврат Текущий; // элемент, на котором замкнулся цикл
КонецЕсли;
Пройденные.Вставить(Текущий, Истина);
Текущий = Текущий.Родитель;
КонецЦикла;
Возврат Неопределено; // цикла нет
КонецФункции
Функция идёт вверх по родителям и возвращает элемент, на котором петля замкнулась. Пустое значение — цикла нет.
Исправление ошибок в коде конфигурации
Исправление зависит от типа петли. Ниже — три самых частых случая.
Повторная запись объекта
Плохой вариант: реквизит меняют уже после записи и записывают объект снова.
Процедура ПриЗаписи(Отказ)
ОбъектДляИзменения = Ссылка.ПолучитьОбъект();
ОбъектДляИзменения.Комментарий = "Проверено";
ОбъектДляИзменения.Записать(); // снова вызовет ПриЗаписи
КонецПроцедуры
Правильно — заполнять реквизиты до записи, в ПередЗаписью, и не вызывать Записать() повторно:
Процедура ПередЗаписью(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Комментарий = "Проверено";
КонецПроцедуры
Связанные объекты, которые пишут друг друга
Когда без записи соседнего объекта не обойтись, ставьте флаг через ДополнительныеСвойства. Флаг живёт только в памяти, в базу не попадает:
Процедура ПриЗаписи(Отказ)
Если ДополнительныеСвойства.Свойство("ЗаписьИзСвязанного") Тогда
Возврат;
КонецЕсли;
Связанный = СвязанныйДокумент.ПолучитьОбъект();
Связанный.ДополнительныеСвойства.Вставить("ЗаписьИзСвязанного", Истина);
Связанный.Записать();
КонецПроцедуры
Тот же флаг проверяет обработчик второго объекта. Петля разрывается на первом же витке.
Рекурсия без ограничения глубины
Для рекурсивных функций добавьте параметр глубины и прерывайте обход на разумном пределе:
Процедура ОбойтиСостав(Изделие, Глубина = 0)
Если Глубина > 50 Тогда
ВызватьИсключение СтрШаблон("Глубина состава больше 50 уровней: %1", Изделие);
КонецЕсли;
// ... обработка
// для каждого вложенного: ОбойтиСостав(Вложенное, Глубина + 1);
КонецПроцедуры
Вместо падения платформы пользователь увидит понятное сообщение с именем проблемного элемента.
Типовую конфигурацию не правьте напрямую — снимете её с поддержки. Исправления делайте в расширении. Перед загрузкой расширения в рабочую базу сделайте резервную копию.
Исправление цикла в данных
Если запрос или функция нашли цикл, откройте проблемный элемент и смените ему родителя на правильную группу или очистите поле. Цикл разрывается одним изменением — достаточно поправить одно звено.
Если форма элемента не открывается из-за той же ошибки, исправьте данные обработкой на копии: получите объект, очистите Родитель, запишите. После проверки повторите на рабочей базе.
Цикл в отчёте СКД
Если отчёт падает только у одного пользователя, дело часто в его настройках. Откройте настройки отчёта, перейдите к пользовательским полям и проверьте выражения вычисляемых полей. Поле «Наценка» не должно ссылаться на «Цену продажи», если «Цена продажи» сама считается через «Наценку».
Быстрая проверка: выберите для отчёта стандартные настройки. Если отчёт построился — ошибка в сохранённом варианте пользователя, а не в конфигурации. Пересоздайте вариант без взаимных ссылок.
Очистка кэша и временных файлов 1С
Кэш не создаёт зацикливание, но может «помнить» старую версию кода. Типичная ситуация: ошибку исправили в расширении, а у пользователя она остаётся.
Кэш на клиенте
Самый мягкий способ — запуск с параметром /ClearCache. В окне выбора базы нажмите «Изменить», дойдите до последней страницы и в поле дополнительных параметров запуска впишите:
/ClearCache
После одного успешного входа параметр можно убрать.
Радикальный вариант — удалить каталоги кэша вручную. Закройте все окна 1С, затем удалите папки с длинными именами-идентификаторами в каталогах:
- 📁
%APPDATA%\1C\1cv8 - 📁
%LOCALAPPDATA%\1C\1cv8
Файлы со списком баз (ibases.v8i) лежат в %APPDATA%\1C\1CEStart — их не трогайте.
Кэш на сервере
Сеансовые данные сервера хранятся в каталоге srvinfo кластера. Их чистят только при остановленной службе агента сервера.
Остановка службы сервера 1С обрывает сеансы всех пользователей всех баз кластера. Делайте это в нерабочее время и предупредите людей заранее. Перед удалением сохраните копию каталога.
К серверному кэшу переходите, только если ошибка воспроизводится у всех после исправления кода и клиентский кэш уже очищен.
Профилактика зацикливания: правила для кода и данных
Большинство петель предотвращают несколько привычек при разработке.
- ✅ Заполняйте реквизиты в
ПередЗаписью, а не перезаписывайте объект вПриЗаписи. - ✅ В каждом обработчике записи проверяйте
ОбменДанными.Загрузкаи выходите сразу, если флаг установлен. - ✅ Для взаимной записи объектов используйте флаги в
ДополнительныеСвойства. - ✅ Любую рекурсию снабжайте счётчиком глубины или соответствием пройденных элементов.
- ✅ При загрузке данных из файлов проверяйте иерархию до записи: родитель не должен быть потомком.
- ✅ После доработок прогоняйте сценарий на копии базы, а не на рабочей.
Для данных полезно раз в квартал запускать проверку циклов по основным иерархическим справочникам. Запрос из раздела про отладку подходит, его можно поставить в регламентное задание в расширении.