Сервер 1С падает с переполнением стека: ищем бесконечную рекурсию
Содержание
Переполнение стека встроенного языка почти всегда означает одно: какая-то процедура вызывает сама себя и не может остановиться. Настройки сервера тут вторичны. Увеличить «память» не получится: глубина вызовов ограничена платформой, и лечится ошибка в коде, а не в параметрах кластера.
Сообщение выглядит так: Переполнение стека встроенного языка (иногда с припиской о превышении допустимой глубины вызовов). На сервере оно прилетает пользователю в окне ошибки, а в журнале регистрации и технологическом журнале остаётся след с цепочкой вызовов. По нему и работаем.
Ниже: откуда рекурсия берётся на практике, как быстро найти виновника, что делать прямо сейчас и как не допускать повторения.
Что такое стек вызовов и почему он кончается
Каждый вызов процедуры или функции кладёт на стек служебную запись: куда вернуться, какие локальные переменные и параметры. Когда процедура завершилась, запись снимается. Стек не резиновый: у платформы есть предельная глубина вложенных вызовов. Если вызовы идут цепочкой без возврата, лимит исчерпывается, и платформа аварийно прерывает выполнение.
Что важно знать:
- 🔁 Ошибка возникает при глубокой цепочке вызовов, а не при долгой работе. Цикл на миллион итераций стек не переполнит, а рекурсия в несколько тысяч уровней — запросто.
- 🧱 Лимит жёсткий. Объём оперативной памяти сервера на него не влияет.
- 🖥 Серверный вызов обрывается только в рамках текущего сеанса. Рабочий процесс
rphostпри этом, как правило, продолжает жить, а вот пользователь теряет операцию.
Рекурсия сама по себе не порок. Обход дерева подразделений или номенклатуры рекурсией — нормальный приём. Проблема в рекурсии без условия выхода или с условием, которое не срабатывает на конкретных данных.
Как проявляется ошибка на сервере
Симптомы бывают разные, но у них общее: сбой воспроизводится на одной и той же операции.
- ❌ Проведение или запись конкретного документа заканчивается ошибкой, остальные документы того же вида проходят.
- ❌ Отчёт падает при определённых параметрах или периоде.
- ❌ Регламентное задание завершается с ошибкой, а в списке заданий видна запись со статусом ошибки.
- ❌ После обновления конфигурации или подключения расширения ошибка появилась там, где её раньше не было.
- ❌ Обмен данными зависает на одном объекте и повторяет сбой при каждой попытке.
Отличите это от других сбоев. Если падает всё и у всех, ищите проблему в самом сервере или базе. Если ошибка привязана к одному объекту или действию, это почти наверняка рекурсия в прикладном коде.
| Признак | Что это значит |
|---|---|
| Падает один документ или один отчёт | Рекурсия на конкретных данных |
| Ошибка появилась после обновления или расширения | Новый код вступил в конфликт со старым |
| Ошибка у всех пользователей на любой операции | Не рекурсия; смотрите состояние кластера и базы |
| Ошибка только на сервере, в файловой базе нет | Другой путь выполнения: серверный модуль или фоновое задание |
Где в коде живёт бесконечная рекурсия
Прямую рекурсию (процедура вызывает сама себя) видно сразу. Тяжелее с косвенной, когда цепочка идёт через несколько модулей. Типичные сценарии:
Подписка на событие, которая меняет тот же объект
Самый частый случай. Подписка на ПриЗаписи или ПередЗаписью получает объект, что-то в нём правит и снова вызывает Записать(). Запись вызывает то же событие, подписка срабатывает снова — и так до переполнения.
Проверка простая: если в обработчике есть Объект.Записать() или Документ.Записать() для того же самого объекта, это подозреваемый номер один.
Две подписки, которые перезаписывают друг друга
Первая подписка меняет документ A и пишет его. Запись A запускает вторую подписку, которая пишет связанный документ B. Запись B запускает первую подписку на тот же A. Круг замкнулся.
Рекурсивный обход иерархии с циклической ссылкой
Процедура обходит родителей элемента справочника: берёт Родитель, затем его родителя и так далее. Если в данных элемент оказался собственным предком (прямо или через цепочку), конец дерева не наступает никогда.
Условие выхода, которое не срабатывает
Выход написан как Если Значение = 0, а на реальных данных приходит Неопределено или пустая ссылка. Условие ложно всегда, рекурсия бесконечна.
Переопределённый метод в расширении
Расширение перехватывает процедуру через &Вместо и в теле вызывает её же без ПродолжитьВызов(), а напрямую. Либо два расширения перехватывают одну процедуру и вызывают друг друга.
Как найти место, где крутится рекурсия
Идём от безопасного способа к более трудоёмким.
Способ 1. Текст ошибки и журнал регистрации
Откройте журнал регистрации: Администрирование → Обслуживание → Журнал регистрации. Отфильтруйте по уровню «Ошибка» и времени сбоя. Подробное представление ошибки обычно содержит имя модуля и строку, с которой начался вызов. Этого иногда хватает, чтобы сразу увидеть процедуру.
Способ 2. Технологический журнал
Журнал регистрации показывает конец цепочки, а техжурнал — полный стек. Для этого в каталоге конфигурации сервера создаётся файл logging.cfg (каталог conf рядом с установкой платформы) с настройкой на исключения:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\techlog\stack" history="24">
<event>
<eq property="name" value="EXCP"/>
</event>
<property name="all"/>
</log>
</config>
Каталог C:\techlog\stack должен существовать, а у учётной записи, под которой работает сервер, должны быть права на запись. Воспроизведите ошибку и откройте свежие файлы журнала: в записи EXCP будет текст исключения и контекст — последовательность вызовов.
Техжурнал с <property name="all"/> быстро разрастается. Включайте его на время воспроизведения и отключайте после. Следите за свободным местом на диске, где лежит каталог журнала.
В контексте ищите повторяющийся фрагмент: одна и та же пара «модуль : процедура» идёт десятки раз подряд. Это и есть петля.
Способ 3. Отладка в Конфигураторе
Если можно воспроизвести на тестовой копии базы:
- Откройте Конфигуратор и включите отладку для нужного сеанса (меню «Отладка»).
- Задайте остановку по ошибке: в параметрах отладки включите остановку при возникновении ошибки (пункт про «Остановку по ошибке» в меню «Отладка»).
- Запустите операцию в режиме «Предприятие» с отладкой.
- Когда отладчик остановится, откройте окно «Стек вызовов» и прокрутите вверх: повторяющиеся строки покажут петлю.
Отлаживать удобнее на копии: на рабочей базе остановка точек останова на сервере блокирует работу других пользователей.
Способ 4. Поиск по конфигурации
Если место примерно ясно, откройте конфигурацию и воспользуйтесь поиском по всей конфигурации (меню «Правка» или «Конфигурация → Поиск по конфигурации»). Ищите .Записать( в модулях объектов и подписок, на которые падает ошибка.
Что делать, если ошибка уже случилась
Порядок действий, чтобы вернуть работу и не потерять данные:
- Зафиксируйте контекст: кто, какая операция, какой объект, во сколько. Это сэкономит часы в техжурнале.
- Определите, что ломается: один объект или все. Если один, остальных пользователей можно не трогать.
- Временно исключите причину: отключите подозрительную подписку, расширение или регламентное задание. На рабочей базе делайте это через согласование, а перед любыми изменениями сделайте резервную копию.
- Верните данные в работоспособное состояние: если дело в циклической ссылке в справочнике, поправьте родителя вручную или обработкой.
- Исправьте код и проверьте на копии на тех же данных, на которых падало.
Не лечите ошибку перезапуском службы сервера. Операция упрётся в тот же лимит сразу после повторного запуска, а остальные пользователи потеряют сеансы зря.
Как исправить код
Выбор зависит от причины.
| Причина | Исправление |
|---|---|
| Повторная запись объекта в обработчике его же записи | Правьте реквизиты прямо в ПередЗаписью, без вызова Записать(). Если запись всё же нужна, поставьте флаг в ДополнительныеСвойства и выходите из обработчика, когда флаг стоит |
| Две подписки зациклены | Разорвите круг флагом в ДополнительныеСвойства или объедините логику в одну подписку |
| Обход иерархии | Накапливайте уже пройденные ссылки в массиве или соответствии и прерывайтесь на повторе |
| Условие выхода не срабатывает | Проверяйте на Неопределено и пустую ссылку, а не только на ноль |
| Расширение | Используйте ПродолжитьВызов() вместо прямого повторного вызова |
Пример защиты от зацикливания через дополнительные свойства:
Процедура ПриЗаписиОбработчик(Источник, Отказ)
Если Источник.ДополнительныеСвойства.Свойство("ПропуститьПересчет") Тогда
Возврат;
КонецЕсли;
Источник.ДополнительныеСвойства.Вставить("ПропуститьПересчет", Истина);
// ... изменения и повторная запись ...
КонецПроцедуры
Флаг живёт в объекте, пока он находится в памяти, поэтому повторный вход в обработчик из того же вызова сразу выходит.
Если рекурсия нужна по смыслу (обход глубокого дерева), перепишите её в цикл с собственным стеком: массив ссылок, из которого берём элементы, пока он не пуст. Такой вариант не упирается в лимит платформы и работает быстрее.
Серверные настройки: что реально можно сделать
Лимит глубины стека настройками кластера не меняется, но есть смежные вещи, которые помогают пережить сбой.
- 🛡 Перезапуск рабочих процессов. В свойствах кластера задаются интервал перезапуска и допустимый объём памяти процессов. Это защищает от накопления мусора, но рекурсию не лечит.
- 📊 Мониторинг. Следите за ростом памяти процесса
rphostна одной и той же операции: бесконечная рекурсия сопровождается резким скачком. - 🧾 Регламентные задания. Для заданий, которые падают с этой ошибкой, включите уведомление о сбое, чтобы не узнавать о нём от пользователей.
Если клиент-серверная база работает на старой платформе, проверьте номер релиза платформы в справке «О программе» и сверьтесь с описанием исправлений в нотах релиза: часть ошибок поведения на сервере исправляют обновлением. Но сначала исключите свой код.
Профилактика
Рекурсию проще не допускать, чем искать.
Что ещё помогает на практике:
- 🧪 Тестируйте на копии рабочей базы. На чистой демонстрационной базе циклических данных нет, и рекурсия там не проявится.
- 🔍 На код-ревью смотрите отдельно любые вызовы
Записать()внутри подписок. - 📝 Описывайте в комментарии, какие подписки срабатывают на один и тот же объект. Через год никто не вспомнит, что одна из них зависит от другой.
Частые вопросы
Можно ли увеличить глубину стека в настройках сервера?
Почему ошибка есть на сервере, а в файловой базе нет?
Это ошибка платформы или нашей конфигурации?
Поможет ли перезапуск сервера?
Как понять, какая именно процедура зацикливается?
EXCP найдите повторяющуюся пару «модуль : процедура» в стеке. Либо остановите отладчик по ошибке и посмотрите стек вызовов.