При разработке интеграционных решений на платформе 1С:Предприятие мы привыкли полагаться на базовые принципы работы системы. Одним из таких аксиом является то, что если у нас есть две ссылки на один и тот же объект базы данных, то их программное сравнение должно возвращать Истина. Однако в контексте работы HTTP-сервисов эта аксиома иногда нарушается, вводя разработчиков в ступор.
В этой статье мы подробно разберем ситуацию, когда идентичные ссылки при сравнении дают результат Ложь, выясним технические причины этого поведения, связанные с кэшированием и пулом соединений, и рассмотрим надежные программные способы обхода этой ошибки.
Рассмотрим типичный сценарий, с которым столкнулись участники обсуждения. У нас есть опубликованный HTTP-сервис, который принимает входящий запрос. В параметрах запроса (или в теле) передается идентификатор клиента, по которому мы находим ссылку в справочнике. Далее мы пытаемся найти этого клиента в табличной части другого объекта или просто сравнить с имеющимися данными.
Код, который вызывает ошибку, выглядит совершенно стандартно и логично:
Доступен = Ложь;
// Обходим коллекцию строк (например, табличную часть)
Для каждого ТекущаяСтрока Из ПользовательЛичногоКабинета.Клиенты Цикл
// Сравниваем ссылку в строке с искомой ссылкой
Если ТекущаяСтрока.Клиент = Клиент Тогда
Доступен = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Возврат Доступен;
Проблема проявляется следующим образом:
Если ТекущаяСтрока.Клиент = Клиент начинает возвращать Ложь, даже если в отладчике видно, что это один и тот же элемент справочника.При этом визуальная проверка через "Табло" в отладчике показывает, что ссылки идентичны. Даже метод ЗначениеВСтрокуВнутр() может показывать одинаковые значения, но оператор равенства все равно не срабатывает.
Давайте разберемся, что происходит "под капотом". HTTP-сервисы в 1С работают в рамках рабочих процессов сервера, но их жизненный цикл отличается от обычного клиентского сеанса.
1. Повторное использование сеансов (Session Reuse)
Для оптимизации производительности веб-сервисы по умолчанию используют механизм пулинга сеансов. Это значит, что при завершении обработки HTTP-запроса сеанс 1С не уничтожается, а "засыпает" и используется для следующего запроса. Это позволяет экономить время на инициализацию.
2. Рассинхронизация кэша объектов
Ошибка кроется в механизме управления памятью внутри процесса rphost или рабочего процесса веб-сервера (при использовании модулей расширения веб-сервера). Когда сеанс переиспользуется, в памяти могут существовать несколько экземпляров C++ объектов, представляющих одну и ту же ссылку в базе данных.
Оператор = в 1С для ссылочных типов пытается оптимизировать сравнение. В некоторых случаях (особенно при передаче данных через границы процессов или при сложной сериализации XDTO) платформа воспринимает две ссылки как разные объекты в памяти, не проводя глубокого сравнения их уникальных идентификаторов (UUID).
3. Проблемы маршалинга
Если одна ссылка была получена из параметров текущего вызова (десериализована из JSON/XML), а вторая была считана из базы данных или кэша сеанса, они могут принадлежать разным контекстам. В результате платформа "не узнает" в них один и тот же объект.
Самый надежный способ обойти глюк платформы — отказаться от сравнения сложных ссылочных типов в пользу примитивных типов (строк). У каждой ссылки есть Уникальный Идентификатор (UUID), который неизменен. Сравнивая строковые представления UUID, мы гарантируем, что сравниваем суть данных, а не указатели в памяти.
Рассмотрим, как правильно переписать проблемный код. Мы будем использовать приведение к строке.
Функция КлиентДоступен(ПользовательЛичногоКабинета, ИскомыйКлиент)
Доступен = Ложь;
// Преобразуем искомую ссылку в строку UUID
// ВАЖНО: Приводим к верхнему регистру для надежности,
// хотя UUID обычно возвращается в одном регистре.
УИДИскомогоКлиента = ВРег(Строка(ИскомыйКлиент.УникальныйИдентификатор()));
Для каждого ТекущаяСтрока Из ПользовательЛичногоКабинета.Клиенты Цикл
// Получаем UUID ссылки из строки таблицы
УИДКлиентаИзБазы = ВРег(Строка(ТекущаяСтрока.Клиент.УникальныйИдентификатор()));
// Сравниваем строки. Примитивные типы сравниваются по значению, а не по ссылке.
Если УИДКлиентаИзБазы = УИДИскомогоКлиента Тогда
Доступен = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Возврат Доступен;
КонецФункции
Почему это работает? Строка — это примитивный тип данных. При сравнении строк платформа посимвольно сверяет их содержимое. Ей не нужно обращаться к кэшу объектов или проверять соответствие указателей в памяти. Если UUID совпадают — это гарантированно один и тот же объект.
Вместо конструкции Строка(Ссылка.УникальныйИдентификатор()) можно использовать глобальный метод XMLСтрока(Ссылка). Он работает чуть быстрее и сразу возвращает строковое представление ссылки (которое по сути и является UUID).
// Оптимизированный вариант сравнения
Если XMLСтрока(ТекущаяСтрока.Клиент) = XMLСтрока(ИскомыйКлиент) Тогда
// Это один и тот же элемент
КонецЕсли;
Если логика позволяет, лучше избегать перебора коллекций в коде и использовать язык запросов. Запросы выполняются движком СУБД/сервера 1С, где механизмы сравнения ссылок работают более стабильно, так как оперируют данными напрямую.
Посмотрим, как можно заменить цикл на поиск через метод НайтиСтроки или запрос (в случае работы с БД):
// Вариант с НайтиСтроки (работает с коллекцией в памяти)
// В теме указано, что НайтиСтроки тоже может сбоить из-за того же глюка сравнения,
// но иногда создание новой структуры отбора помогает "заземлить" тип.
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Клиент", Клиент);
НайденныеСтроки = ПользовательЛичногоКабинета.Клиенты.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
Возврат Истина;
КонецЕсли;
Однако, участники обсуждения отметили, что даже НайтиСтроки и методы ОбщегоНазначения.ЗначениеРеквизитаОбъекта могут давать сбой в таких условиях. Поэтому вариант со сравнением строк UUID (Решение 1) остается приоритетным для HTTP-сервисов.
Если вы не хотите менять код или ошибка проявляется в типовых модулях, которые нельзя править, можно изменить поведение самого веб-сервиса, отключив переиспользование сеансов.
В файле публикации default.vrd (который лежит в папке публикации на веб-сервере) можно добавить атрибут reuseSessions="false" в тег point вашего сервиса или глобально.
Пример настройки:
<service name="MyService" rootUrl="myservice" enable="true" reuseSessions="false"/>
Последствия:
Если вы используете веб-сервер IIS, можно настроить Recycling (Рециклинг) пула приложений, обслуживающего публикацию 1С. Это "золотая середина" между ручным перезапуском и полным отключением пулинга.
Это заставит IIS перезапускать рабочий процесс w3wp.exe автоматически после обработки заданного числа запросов, очищая накопившиеся ошибки памяти, но сохраняя скорость работы в рамках пакета запросов.
Проблема неравенства ссылок в HTTP-сервисах 1С — это известная особенность платформы, связанная с управлением памятью в долгоживущих сеансах. Мы проанализировали несколько путей решения:
Если Ссылка1 = Ссылка2 на сравнение их строковых идентификаторов Если XMLСтрока(Ссылка1) = XMLСтрока(Ссылка2). Это полностью устраняет зависимость от глюков кэша.reuseSessions (с осторожностью).Используйте сравнение UUID во всех критичных местах логики HTTP-сервисов, чтобы гарантировать стабильность работы ваших интеграций.