В практике разработки на платформе 1С:Предприятие 8.3 иногда встречается пугающая ситуация: запущенная серверная процедура, выполняющая длительную обработку данных (например, выгрузку тяжелых пакетов через КД 2.0), внезапно прерывается и начинает свое выполнение с самой первой строки. При этом отладчик может повторно останавливаться на точке останова в начале процедуры, хотя цикл внутри еще не был завершен. Рассмотрим подробнее, почему это происходит и как устранить этот «мистический» эффект.
Когда мы запускаем длительный процесс на сервере из формы (через обычную команду), клиентское приложение ожидает ответа от сервера. Если информационная база опубликована через веб-сервер (IIS или Apache), в цепочку взаимодействия включается протокол HTTP. Рассмотрим причины, по которым процедура может стартовать заново:
ВыполнитьВыгрузку занимает время, превышающее таймаут веб-сервера (по умолчанию часто 60 или 120 секунд), веб-сервер или промежуточный прокси-сервер (например, Nginx) может посчитать, что запрос «потерялся». В этом случае он автоматически инициирует повторный POST-запрос. Платформа 1С воспринимает это как новый вызов команды пользователем, и процедура запускается во втором потоке с самого начала.УниверсальныйОбменДаннымиXML потребляет значительный объем оперативной памяти. Если потребление памяти превышает допустимые пределы, настроенные в кластере 1С, процесс rphost может быть принудительно завершен. В некоторых случаях клиентское приложение пытается восстановить соединение и повторить последнюю операцию.Проанализируем код, который часто приводит к таким последствиям. В нем создается объект обработки внутри цикла, что само по себе ресурсозатратно:
&НаСервере
Процедура ВыгрузитьДанные()
ВсеПакетыВыгружены = Ложь;
Пока НЕ ВсеПакетыВыгружены Цикл
// Создание объекта в цикле создает лишнюю нагрузку на память
Обработка = ВнешниеОбработки.Создать(ПутьКФайлу, Ложь);
Обработка.РежимОбмена = "Выгрузка";
// ... настройка параметров ...
Обработка.ВыполнитьВыгрузку(); // Здесь происходит длительная пауза
// Условие выхода
Если ТекущийКод >= МаксимальныйНомер Тогда
ВсеПакетыВыгружены = Истина;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Чтобы избежать зависимости от таймаутов HTTP-соединения и «зависания» интерфейса пользователя, длительные операции необходимо выносить в фоновые задания. Это кардинально решает проблему повторных вызовов, так как клиент не ждет завершения HTTP-запроса, а просто запускает процесс на сервере и периодически проверяет его состояние.
Разберем, как правильно перевести выгрузку в фон. Вместо прямого вызова процедуры из команды формы, используем менеджер фоновых заданий:
&НаКлиенте
Процедура ВыполнитьВыгрузку(Команда)
ПараметрыВыгрузки = Новый Структура;
ПараметрыВыгрузки.Вставить("ДатаВыгрузки", Объект.ДатаВыгрузки);
ПараметрыВыгрузки.Вставить("ПутьКФайлуПравил", Объект.ПутьКФайлуПравил);
// Запускаем фоновое задание
ФоновоеЗадание = ЗапуститьВыгрузкуНаСервере(ПараметрыВыгрузки);
ИдентификаторЗадания = ФоновоеЗадание.УникальныйИдентификатор;
// Подключаем обработчик ожидания для проверки статуса
ПодключитьОбработчикОжидания("ПроверитьСтатусВыгрузки", 5);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ЗапуститьВыгрузкуНаСервере(ПараметрыВыгрузки)
Возврат ФоновыеЗадания.Выполнить("МодульСерверныхПроцедур.ВыполнитьДлительнуюВыгрузку", ПараметрыВыгрузки);
КонецФункции
В этом случае процедура ВыполнитьДлительнуюВыгрузку будет работать в отдельном сеансе, который не ограничен таймаутом веб-сервера Keep-Alive.
Создание экземпляра УниверсальныйОбменДаннымиXML на каждой итерации цикла — крайне неэффективный путь. Каждая инициализация требует загрузки правил обмена и инициализации метаданных. Рассмотрим, как оптимизировать код, создав объект один раз перед циклом:
&НаСервере
Процедура ВыгрузитьДанныеОптимизировано()
// Создаем объект один раз
ОбработкаОбъект = Обработки.УниверсальныйОбменДаннымиXML.Создать();
ОбработкаОбъект.РежимОбмена = "Выгрузка";
ОбработкаОбъект.ИмяФайлаПравилОбмена = Объект.ПутьКФайлуПравил;
ОбработкаОбъект.ЗагрузитьПравилаОбмена();
// Получаем ссылки на параметры заранее
ПараметрНачало = ОбработкаОбъект.ТаблицаНастройкиПараметров.Найти("ДатаНачала", "Имя");
ПараметрКонец = ОбработкаОбъект.ТастройкаПараметров.Найти("ДатаОкончания", "Имя");
Пока ТекКодПервый <= Объект.МаксимальныйНомер Цикл
// Меняем только значения параметров и имя файла
ОбработкаОбъект.ИмяФайлаОбмена = ПолучитьИмяФайла(ТекКодПервый);
УстановитьЗначенияПараметров(ОбработкаОбъект, ТекКодПервый);
// Выполняем выгрузку порции данных
ОбработкаОбъект.ВыполнитьВыгрузку();
// Инкремент счетчиков
ТекКодПервый = ТекКодПервый + Объект.ШагПакета;
КонецЦикла;
КонецПроцедуры
Такой подход значительно снижает нагрузку на память (RAM) и уменьшает общее время выполнения, что косвенно снижает вероятность возникновения таймаута.
Если перевод на фоновые задания по каким-то причинам невозможен, следует проанализировать настройки веб-сервера. Выясним причину обрыва соединения в логах. Для IIS следует увеличить значения параметров Connection Timeout и настройки CGI/FastCGI Timeout. Для Apache обратите внимание на директивы Timeout и KeepAliveTimeout.
Важный момент: Если между клиентом и сервером стоит прокси-сервер (например, при работе через облако или Nginx), таймауты нужно увеличивать и на нем (параметры proxy_read_timeout в Nginx).
Чтобы окончательно убедиться, что происходит именно повторный вызов, рекомендуется настроить Технологический журнал (ТЖ). Нас интересуют события CALL (вызовы серверных методов) и EXCP (исключения).
Если в ТЖ вы видите два события CALL для одной и той же процедуры с небольшим интервалом, при этом первое не завершилось событием RETN (возврат), значит, имеет место дублирование запроса со стороны сетевой инфраструктуры.
Резюмируем:
rphost в консоли администрирования кластера 1С.