Бесконечная рекурсия и циклы в иерархии: как найти виновника в 1С 8.3

· 9 мин

Содержание

Зацикливание уровней в 1С 8.3 почти всегда означает одно из двух: код вызывает сам себя без выхода (рекурсия до переполнения стека) или в данных появился цикл — элемент справочника стал «родителем» собственного предка. Первое исправляют в коде конфигурации, второе — в данных базы. Чистка кэша помогает только тогда, когда ошибка осталась после исправления.

Отличить одно от другого можно за 10 минут: если ошибка возникает на конкретном элементе или документе — ищите цикл в данных. Если на любом объекте при одной и той же операции — ищите рекурсию в коде.

Как выглядит зацикливание и чем оно отличается от «зависания»

Зацикливание — это не медленная работа. Медленный отчёт рано или поздно закончится, а зацикленный код будет углубляться, пока не кончится память стека.

Типичные симптомы:

  • 🔁 сообщение со словами «переполнение стека» или Stack overflow;
  • 💥 клиент 1С закрывается без сообщения при записи документа или открытии формы;
  • 🖥️ на сервере аварийно перезапускается рабочий процесс rphost, у всех пользователей этого процесса обрываются сеансы;
  • ⏳ форма «думает» несколько секунд, память процесса растёт, затем — ошибка;
  • 🌳 в иерархическом списке группа раскрывается сама в себя, или отчёт по иерархии не строится.
ПризнакСкорее всегоГде искать
Ошибка на одном элементе справочникаЦикл в иерархии данныхПоле «Родитель» элемента и его предков
Ошибка при записи любого документа видаРекурсия в обработчике записиМодуль объекта, подписки на события
Ошибка после обновления или доработкиНовый код или расширениеПоследние изменения конфигурации
Ошибка только у одного пользователяПовреждённый кэш или настройкиКаталог кэша этого пользователя
Ошибка при построении отчёта СКДВычисляемые поля ссылаются друг на другаНастройки схемы компоновки

Диагностика зацикливания через журнал регистрации

Журнал регистрации — первое место, куда стоит смотреть. Он покажет, кто, когда и на каком объекте получил ошибку.

В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) журнал открывается из раздела Администрирование → Обслуживание. В Конфигураторе — через меню Администрирование → Журнал регистрации.

Порядок работы:

  1. Установите отбор по уровню важности — «Ошибка».
  2. Сузьте период до времени, когда пользователь получил сообщение.
  3. Отберите по пользователю, если ошибка воспроизводится у одного человека.
  4. Откройте запись и посмотрите поля «Данные» и «Метаданные» — там будет объект, на котором всё упало.
  5. В комментарии события ищите текст ошибки и имя модуля с номером строки.

Если в нескольких записях подряд фигурирует один и тот же элемент справочника, это сильный аргумент в пользу цикла в данных, а не в коде.

Журнал не всегда фиксирует падение. Когда процесс 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. Выгрузите копию базы и разверните её как отдельную информационную базу.
  2. Откройте копию в Конфигураторе, включите Отладка → Остановка по ошибке.
  3. Запустите клиент из Конфигуратора: Отладка → Начать отладку и выберите тип клиента.
  4. Повторите действие, на котором возникает ошибка.
  5. Когда отладчик остановится, откройте окно Отладка → Стек вызовов.

Стек вызовов — главный инструмент. При зацикливании в нём видна повторяющаяся пара или тройка процедур: ПриЗаписи → ОбработатьСвязанные → ПриЗаписи → ОбработатьСвязанные… Это и есть петля. Ищите, в каком месте цепочки должен был сработать выход.

Для отладки кода на сервере в клиент-серверном варианте сервер 1С должен быть запущен с разрешённой отладкой. На рабочем сервере это включают только по согласованию — отладка нагружает сервер.

Если ошибка «плавающая», поставьте точку останова с условием. Например, по счётчику: остановиться, когда процедура вызвана в сотый раз. Нормальный код столько вложенных вызовов не делает.

Проверка данных на цикл

Цикл, где элемент — родитель самого себя, находится одним запросом. Запустите его в консоли запросов на копии базы:

ВЫБРАТЬ
    Справочник.Ссылка,
    Справочник.Наименование
ИЗ
    Справочник.Номенклатура КАК Справочник
ГДЕ
    Справочник.Родитель = Справочник.Ссылка

Длинный цикл (А → Б → В → А) одним запросом не поймать. Его проще найти обходом с запоминанием пройденных элементов:

Функция НайтиЦиклВИерархии(Элемент) Экспорт

    Пройденные = Новый Соответствие;
    Текущий = Элемент;

    Пока ЗначениеЗаполнено(Текущий) Цикл
        Если Пройденные[Текущий] <> Неопределено Тогда
            Возврат Текущий; // элемент, на котором замкнулся цикл
        КонецЕсли;
        Пройденные.Вставить(Текущий, Истина);
        Текущий = Текущий.Родитель;
    КонецЦикла;

    Возврат Неопределено; // цикла нет

КонецФункции

Функция идёт вверх по родителям и возвращает элемент, на котором петля замкнулась. Пустое значение — цикла нет.

Исправление ошибок в коде конфигурации

Исправление зависит от типа петли. Ниже — три самых частых случая.

Повторная запись объекта

Плохой вариант: реквизит меняют уже после записи и записывают объект снова.

Процедура ПриЗаписи(Отказ)
    ОбъектДляИзменения = Ссылка.ПолучитьОбъект();
    ОбъектДляИзменения.Комментарий = "Проверено";
    ОбъектДляИзменения.Записать(); // снова вызовет ПриЗаписи
КонецПроцедуры

Правильно — заполнять реквизиты до записи, в ПередЗаписью, и не вызывать Записать() повторно:

Процедура ПередЗаписью(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    Комментарий = "Проверено";
КонецПроцедуры

Связанные объекты, которые пишут друг друга

Когда без записи соседнего объекта не обойтись, ставьте флаг через ДополнительныеСвойства. Флаг живёт только в памяти, в базу не попадает:

Процедура ПриЗаписи(Отказ)
    Если ДополнительныеСвойства.Свойство("ЗаписьИзСвязанного") Тогда
        Возврат;
    КонецЕсли;

    Связанный = СвязанныйДокумент.ПолучитьОбъект();
    Связанный.ДополнительныеСвойства.Вставить("ЗаписьИзСвязанного", Истина);
    Связанный.Записать();
КонецПроцедуры

Тот же флаг проверяет обработчик второго объекта. Петля разрывается на первом же витке.

Рекурсия без ограничения глубины

Для рекурсивных функций добавьте параметр глубины и прерывайте обход на разумном пределе:

Процедура ОбойтиСостав(Изделие, Глубина = 0)
    Если Глубина > 50 Тогда
        ВызватьИсключение СтрШаблон("Глубина состава больше 50 уровней: %1", Изделие);
    КонецЕсли;
    // ... обработка
    // для каждого вложенного: ОбойтиСостав(Вложенное, Глубина + 1);
КонецПроцедуры

Вместо падения платформы пользователь увидит понятное сообщение с именем проблемного элемента.

Типовую конфигурацию не правьте напрямую — снимете её с поддержки. Исправления делайте в расширении. Перед загрузкой расширения в рабочую базу сделайте резервную копию.

Исправление цикла в данных

Если запрос или функция нашли цикл, откройте проблемный элемент и смените ему родителя на правильную группу или очистите поле. Цикл разрывается одним изменением — достаточно поправить одно звено.

Если форма элемента не открывается из-за той же ошибки, исправьте данные обработкой на копии: получите объект, очистите Родитель, запишите. После проверки повторите на рабочей базе.

Цикл в отчёте СКД

Если отчёт падает только у одного пользователя, дело часто в его настройках. Откройте настройки отчёта, перейдите к пользовательским полям и проверьте выражения вычисляемых полей. Поле «Наценка» не должно ссылаться на «Цену продажи», если «Цена продажи» сама считается через «Наценку».

Быстрая проверка: выберите для отчёта стандартные настройки. Если отчёт построился — ошибка в сохранённом варианте пользователя, а не в конфигурации. Пересоздайте вариант без взаимных ссылок.

Диагностика: выберите симптом

Очистка кэша и временных файлов 1С

Кэш не создаёт зацикливание, но может «помнить» старую версию кода. Типичная ситуация: ошибку исправили в расширении, а у пользователя она остаётся.

Кэш на клиенте

Самый мягкий способ — запуск с параметром /ClearCache. В окне выбора базы нажмите «Изменить», дойдите до последней страницы и в поле дополнительных параметров запуска впишите:

/ClearCache

После одного успешного входа параметр можно убрать.

Радикальный вариант — удалить каталоги кэша вручную. Закройте все окна 1С, затем удалите папки с длинными именами-идентификаторами в каталогах:

  • 📁 %APPDATA%\1C\1cv8
  • 📁 %LOCALAPPDATA%\1C\1cv8

Файлы со списком баз (ibases.v8i) лежат в %APPDATA%\1C\1CEStart — их не трогайте.

Кэш на сервере

Сеансовые данные сервера хранятся в каталоге srvinfo кластера. Их чистят только при остановленной службе агента сервера.

Остановка службы сервера 1С обрывает сеансы всех пользователей всех баз кластера. Делайте это в нерабочее время и предупредите людей заранее. Перед удалением сохраните копию каталога.

К серверному кэшу переходите, только если ошибка воспроизводится у всех после исправления кода и клиентский кэш уже очищен.

Профилактика зацикливания: правила для кода и данных

Большинство петель предотвращают несколько привычек при разработке.

  • ✅ Заполняйте реквизиты в ПередЗаписью, а не перезаписывайте объект в ПриЗаписи.
  • ✅ В каждом обработчике записи проверяйте ОбменДанными.Загрузка и выходите сразу, если флаг установлен.
  • ✅ Для взаимной записи объектов используйте флаги в ДополнительныеСвойства.
  • ✅ Любую рекурсию снабжайте счётчиком глубины или соответствием пройденных элементов.
  • ✅ При загрузке данных из файлов проверяйте иерархию до записи: родитель не должен быть потомком.
  • ✅ После доработок прогоняйте сценарий на копии базы, а не на рабочей.

Для данных полезно раз в квартал запускать проверку циклов по основным иерархическим справочникам. Запрос из раздела про отладку подходит, его можно поставить в регламентное задание в расширении.

Перед выкладкой доработки в рабочую базу
Выполнено: 0 / 5

Частые вопросы о зацикливании уровней

Может ли «Тестирование и исправление» убрать цикл в иерархии?
Не стоит на это рассчитывать. Эта процедура чинит структуру базы и битые ссылки, а логический цикл «элемент — родитель своего предка» ссылочно корректен. Его исправляют сменой родителя.
Почему ошибка появляется на сервере, а в файловой базе всё работает?
Размер стека на сервере и на клиенте разный, и код может выполняться в разных контекстах. Глубокая, но конечная рекурсия иногда проходит в файловом варианте и падает на сервере. Решение одно — убрать лишнюю глубину вызовов.
Ошибка возникла после обмена данными. Что проверить?
Иерархию загруженных справочников. При загрузке обменом проверки обычно отключены, и цикл мог прийти из другой базы. Найдите его запросом и исправьте в базе-источнике, иначе следующий обмен вернёт ошибку.
Можно ли просто увеличить размер стека?
Это лечение симптома. Бесконечная рекурсия переполнит любой стек. Ищите петлю в стеке вызовов отладчика.
Нужно ли перезапускать 1С после исправления кода в расширении?
Да, пользователям нужно перезайти в базу, чтобы получить новую версию. Если ошибка осталась — очистите кэш параметром /ClearCache.