From 1000a9ec67daabd21e4ed433f0fb90b916d782d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 07:27:51 +0000 Subject: [PATCH 01/55] =?UTF-8?q?=D0=9F=D1=83=D0=BB=20=D1=81=D0=BE=D0=B5?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D1=81=D0=B8=D1=82=D1=81=D1=8F=20=D0=B2=20?= =?UTF-8?q?=D0=98=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=94=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85:=20=D0=BB=D0=B5=D0=BD=D0=B8=D0=B2?= =?UTF-8?q?=D0=BE=D0=B5=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?,=20=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D1=8C,=20=D0=B0=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Промежуточное состояние: наборы на устаревшем конструкторе красные, пока не решено умолчание автозакрытия (oneunit исполняет ПередКаждым и тест в разных потоках). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.os" | 132 ++++++++++++- ...20\276\321\201\321\202\320\265\320\271.os" | 70 ++++--- ...20\260\320\275\320\275\321\213\321\205.os" | 177 ++++++++++++++++++ 3 files changed, 338 insertions(+), 41 deletions(-) create mode 100644 "tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index d6790ae..db3374e 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -1,3 +1,6 @@ +#Использовать "../internal" +#Использовать "../internal/concurrent" + #Использовать asserts #Использовать reflector @@ -11,12 +14,37 @@ Перем пПараметрыКоннектора; // Число - Предел одновременно живущих соединений с БД; 0 - без предела. Перем пРазмерПула; +// ПулСоединенийСБД, Неопределено - Пул соединений; создается при первом обращении. +Перем пПул; +// БлокировкаЧтенияЗаписи - Защищает создание и закрытие пула. +Перем БлокировкаПула; +// Булево - Закрывать источник по завершению потока, в котором он создан. +Перем пАвтоЗакрытие; +// Булево - Источник закрыт: пул закрыт и соединений больше не выдает. +Перем пЗакрыт; + +// Вызывается движком по завершению потока, создавшего источник: при включенном автозакрытии +// закрывает источник. Объявлен до конструктора намеренно: имя обработчика движок разрешает +// в один проход, и подписка на метод, объявленный ниже по модулю, не компилируется. +// +// Вызов через "ДобавитьОбработчик" статический анализ не видит, отсюда подавление диагностики. +// +// BSLLS:UnusedLocalMethod-off +Процедура ПриЗавершенииПотока() + Если пАвтоЗакрытие Тогда + Закрыть(); + КонецЕсли; +КонецПроцедуры +// BSLLS:UnusedLocalMethod-on -// Описание источника данных: имя, тип коннектора, строка соединения, параметры коннектора -// и размер пула соединений одним объектом. +// Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула +// и сам пул соединений одним объектом. // -// Источник соединений не открывает, только описывает их. Тип коннектора проверяется при -// создании: тип без интерфейса АбстрактныйКоннектор отвергается с ошибкой. +// Пул создается при первом обращении и общий для всех, кто работает через источник. Закрывает +// пул сам источник: методом Закрыть() или по завершению потока, в котором источник создан, если +// автозакрытие не выключено, см. УстановитьАвтоЗакрытие. Закрытие терминально: закрытый +// источник соединений не выдает. Тип коннектора проверяется при создании: тип без интерфейса +// АбстрактныйКоннектор отвергается с ошибкой. // // Параметры: // Имя - Строка - Имя источника данных. @@ -54,6 +82,14 @@ пПараметрыКоннектора = Новый ФиксированныйМассив(ПараметрыКоннектора); КонецЕсли; + пПул = Неопределено; + БлокировкаПула = Новый БлокировкаЧтенияЗаписи; + пАвтоЗакрытие = Истина; + пЗакрыт = Ложь; + + // Источник живет не дольше создавшего его потока: о завершении сообщит сам поток. + ДобавитьОбработчик ТекущийПоток().ПриЗавершении, ПриЗавершенииПотока; + КонецПроцедуры // Проверяет, что тип коннектора реализует интерфейс АбстрактныйКоннектор. @@ -120,3 +156,91 @@ Функция РазмерПула() Экспорт Возврат пРазмерПула; КонецФункции + +// Включает или выключает закрытие источника по завершению потока, в котором он создан. +// +// По умолчанию включено. Выключает тот, кто управляет временем жизни источника сам: контейнер +// приложения или код, создающий источник в одном потоке для работы в других. Тогда источник +// закрывается только явным Закрыть(). +// +// Параметры: +// Значение - Булево - Истина - закрывать по завершению потока; Ложь - только явным Закрыть(). +// +Процедура УстановитьАвтоЗакрытие(Знач Значение) Экспорт + пАвтоЗакрытие = Значение; +КонецПроцедуры + +// Признак закрытия источника по завершению потока, см. УстановитьАвтоЗакрытие. +// +// Возвращаемое значение: +// Булево - Истина, если источник закрывается по завершению создавшего его потока. +// +Функция АвтоЗакрытие() Экспорт + Возврат пАвтоЗакрытие; +КонецФункции + +// Закрывает пул соединений источника: все его соединения закрываются. +// +// Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный +// вызов ничего не делает. +// +Процедура Закрыть() Экспорт + + БлокировкаПула.Запись().Заблокировать(); + + Попытка + Если пПул <> Неопределено И НЕ пЗакрыт Тогда + пПул.Закрыть(); + КонецЕсли; + пЗакрыт = Истина; + Исключение + БлокировкаПула.Запись().Разблокировать(); + ВызватьИсключение; + КонецПопытки; + + БлокировкаПула.Запись().Разблокировать(); + +КонецПроцедуры + +// Источник закрыт, см. Закрыть. +// +// Возвращаемое значение: +// Булево - Истина после закрытия, явного или по завершению потока. +// +Функция Закрыт() Экспорт + Возврат пЗакрыт; +КонецФункции + +// BSLLS:UsingServiceTag-off +// @internal +// BSLLS:UsingServiceTag-on +// Пул соединений источника; создается при первом обращении. +// +// Возвращаемое значение: +// ПулСоединенийСБД - Пул соединений. +// +Функция ПулСоединений() Экспорт + + Если пПул <> Неопределено И НЕ пЗакрыт Тогда + Возврат пПул; + КонецЕсли; + + БлокировкаПула.Запись().Заблокировать(); + + Попытка + Если пЗакрыт Тогда + ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", пИмя); + КонецЕсли; + Если пПул = Неопределено Тогда + пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); + КонецЕсли; + Исключение + БлокировкаПула.Запись().Разблокировать(); + ВызватьИсключение; + КонецПопытки; + + БлокировкаПула.Запись().Разблокировать(); + + Возврат пПул; + +КонецФункции diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index ba280fe..5b31354 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -8,12 +8,9 @@ // МодельДанных - Хранит данные о типах полей, колонках, настроек таблиц для типов сущностей Перем МодельДанных; -// ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пула. +// ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; -// ПулСоединенийСБД - Выдает потокам исполнения отдельные коннекторы: через него идут все обращения к БД. -Перем ПулСоединений; - // Лог - Журнал библиотеки. Перем Лог; @@ -67,17 +64,18 @@ МодельДанных = Новый МодельДанных; - // Соединений пул заранее не открывает, поэтому его можно завести сразу: коннектор доступен - // до Инициализировать(), как и раньше. Параметры коннектора берутся зафиксированными из источника. - ПулСоединений = Новый ПулСоединенийСБД( - Источник.РазмерПула(), - Источник.ТипКоннектора(), - Источник.СтрокаСоединения(), - Источник.ПараметрыКоннектора() - ); - КонецПроцедуры +// Пул соединений источника данных. Через него идут все обращения менеджера к БД; создает пул +// источник при первом обращении, закрывает тоже он. +// +// Возвращаемое значение: +// ПулСоединенийСБД - Пул соединений источника. +// +Функция Пул() + Возврат Источник.ПулСоединений(); +КонецФункции + // Предупреждает, что вместе с источником данных переданы устаревшие параметры конструктора. // // Параметры: @@ -133,16 +131,16 @@ // Процедура Инициализировать() Экспорт - Соединение = ПулСоединений.Занять(); + Соединение = Пул().Занять(); Попытка СоздатьТаблицы(Соединение.ПолучитьКоннектор()); Исключение - ПулСоединений.Освободить(); + Пул().Освободить(); ВызватьИсключение; КонецПопытки; - ПулСоединений.Освободить(); + Пул().Освободить(); КонецПроцедуры @@ -199,7 +197,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.СохранитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.СохранитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -226,7 +224,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -254,7 +252,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(Пул(),ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -271,21 +269,19 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.УдалитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.УдалитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры -// Выполняет очистку полную данных библиотеки. -// Дополнительно посылает всем используемым коннекторам запросы на закрытие соединения. +// Освобождает модель данных и хранилища сущностей менеджера. +// +// Пул соединений принадлежит источнику данных и здесь не закрывается: соединения закрывает +// ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. // Процедура Закрыть() Экспорт ХранилищаСущностей.Закрыть(Источник.ТипКоннектора(), Источник.СтрокаСоединения()); - Если ПулСоединений <> Неопределено Тогда - ПулСоединений.Закрыть(); - КонецЕсли; - МодельДанных.Очистить(); // Для освобожения ссылок на все коннекторы и соединения с СУБД @@ -299,19 +295,19 @@ // попадают в эту транзакцию, а завершить ее другое фоновое задание не может. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Пул()); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Пул()); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Пул()); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -332,7 +328,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(Пул(),Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -354,7 +350,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(Пул(),Алгоритм, Контекст, Объект); КонецФункции // BSLLS:UsingServiceTag-off @@ -362,16 +358,16 @@ // BSLLS:UsingServiceTag-on // Для служебного пользования. // -// Возвращает пул соединений менеджера. +// Возвращает пул соединений источника данных, из которого создан менеджер. // -// Хранилища сущностей работают тем же пулом, что и менеджер: предел одновременно живущих -// соединений должен быть общим, а транзакция потока - видной из обоих. +// Хранилища сущностей и все менеджеры одного источника работают одним пулом: предел +// одновременно живущих соединений общий, а транзакция потока видна из каждого из них. // // Возвращаемое значение: -// ПулСоединенийСБД - Пул соединений менеджера. +// ПулСоединенийСБД - Пул соединений источника. // Функция ПолучитьПулСоединений() Экспорт - Возврат ПулСоединений; + Возврат Пул(); КонецФункции // Получает ХранилищеСущностей, привязанное к переданному типу сущности. @@ -384,7 +380,7 @@ // Функция ПолучитьХранилищеСущностей(ТипСущности) Экспорт ОбъектМодели = МодельДанных.Получить(ТипСущности); - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, ПулСоединений); + ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Пул()); Возврат ХранилищеСущностей; КонецФункции diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 0000000..9cc91e7 --- /dev/null +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,177 @@ +// Пул соединений принадлежит источнику данных: менеджеры одного источника делят пул и +// транзакцию потока, а закрывает пул источник - явно или по завершению потока, в котором +// источник создан. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off + +#Использовать ".." +#Использовать "utils" + +// ИсточникДанных - Источник, созданный фоновым заданием; главный поток проверяет его после +// завершения задания. +Перем ИсточникФоновогоЗадания; + +&Тест +Процедура МенеджерыОдногоИсточникаДелятПул() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "общий пул"); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + + Ожидаем.Что(Первый.ПолучитьПулСоединений() = Второй.ПолучитьПулСоединений(), "Пул один на источник") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Транзакции нужен коннектор СУБД: КоннекторInMemory их не поддерживает. +&Тест +Процедура ТранзакцияПотокаОбщаяДляМенеджеровИсточника() Экспорт + + Источник = ИсточникСТранзакциями(); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + + Первый.НачатьТранзакцию(); + КоннекторПервого = Первый.ВычислитьСКоннектором("Коннектор -> Коннектор"); + КоннекторВторого = Второй.ВычислитьСКоннектором("Коннектор -> Коннектор"); + Первый.ОтменитьТранзакцию(); + + Ожидаем.Что(КоннекторПервого = КоннекторВторого, "Второй менеджер попадает в транзакцию потока") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ЗакрытиеМенеджераНеЗакрываетПулИсточника() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "живой пул"); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + Первый.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Первый.Закрыть(); + + Ожидаем.Что(Источник.Закрыт(), "Закрытие менеджера источник не закрывает").ЭтоЛожь(); + Ожидаем.Что(Второй.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ПослеЗакрытияИсточникаМенеджерНеРаботает() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытый пул"); + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + + ТекстОшибки = ""; + Попытка + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Обращение через закрытый источник завершается ошибкой").Содержит("закрыт"); + +КонецПроцедуры + +&Тест +Процедура ИсточникЗакрываетсяПоЗавершениюПотока() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникВФоне", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Фоновое задание прошло без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникФоновогоЗадания.Закрыт(), "Источник закрыт по завершению создавшего его потока") + .ЭтоИстина(); + +КонецПроцедуры + +&Тест +Процедура АвтозакрытиеОтключаетсяСеттером() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтозакрытияВФоне", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Фоновое задание прошло без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникФоновогоЗадания.Закрыт(), "Без автозакрытия источник переживает поток").ЭтоЛожь(); + + Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + Ожидаем.Что(Менеджер.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + + ИсточникФоновогоЗадания.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ПовторноеЗакрытиеИсточникаБезошибочно() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "двойное закрытие"); + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + Источник.Закрыть(); + + Ожидаем.Что(Источник.Закрыт()).ЭтоИстина(); + +КонецПроцедуры + +// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL +// по переменным среды. +// +// Возвращаемое значение: +// ИсточникДанных - Источник. +// +Функция ИсточникСТранзакциями() + + Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); + КонецЕсли; + + Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда + СтрокаСоединения = СтрШаблон( + "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") + ); + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + КонецЕсли; + + ВызватьИсключение "Нет доступного коннектора с транзакциями"; + +КонецФункции + +// Создает источник и открывает его пул из фонового задания. Используется тестом +// "ИсточникЗакрываетсяПоЗавершениюПотока". +// +Процедура СоздатьИсточникВФоне() Экспорт + + ИсточникФоновогоЗадания = Новый ИсточникДанных("Фон", Тип("КоннекторInMemory"), "фон"); + Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + +КонецПроцедуры + +// Создает источник без автозакрытия и открывает его пул из фонового задания. Используется тестом +// "АвтозакрытиеОтключаетсяСеттером". +// +Процедура СоздатьИсточникБезАвтозакрытияВФоне() Экспорт + + ИсточникФоновогоЗадания = Новый ИсточникДанных("Фон", Тип("КоннекторInMemory"), "фон без автозакрытия"); + ИсточникФоновогоЗадания.УстановитьАвтоЗакрытие(Ложь); + Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + +КонецПроцедуры From d3f82a3728595d0c12a0cadcc38df467050697bb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 09:57:27 +0000 Subject: [PATCH 02/55] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B0=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B6=D0=B8=D0=B2=D1=83=D1=82=20=D1=81=20=D0=BF?= =?UTF-8?q?=D1=83=D0=BB=D0=BE=D0=BC=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0;=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=D1=8B=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D1=8E=D1=82=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=20=D1=8F=D0=B2=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кэш хранилищ теперь ключуется пулом соединений, а не типом коннектора и строкой соединения, и закрывается вместе с пулом в ИсточникДанных.Закрыть(). Менеджер.Закрыть() только освобождает модель данных. Наборы oneunit исполняют подготовку и тест в разных потоках, поэтому создают источник сами, выключают автозакрытие и закрывают его в ПослеКаждого. Документация ИсточникДанных, МенеджерСущностей, ПотокобезопаснаяРаботаСБД и README описывают пул как принадлежащий источнику. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 4 +- ...20\260\320\275\320\275\321\213\321\205.md" | 71 ++++- ...20\276\321\201\321\202\320\265\320\271.md" | 12 +- ...21\202\320\260\320\241\320\221\320\224.md" | 5 +- ...20\276\321\201\321\202\320\265\320\271.os" | 247 +++++++++--------- ...20\260\320\275\320\275\321\213\321\205.os" | 2 + ...20\276\321\201\321\202\320\265\320\271.os" | 14 +- ...21\202\320\276\321\200\320\276\320\274.os" | 8 +- ...20\276\321\201\321\202\320\265\320\271.os" | 8 +- ...20\264\320\260\320\275\320\270\321\217.os" | 8 +- ...20\277\320\270\321\201\321\214InMemory.os" | 8 +- ...\277\320\270\321\201\321\214PostgreSQL.os" | 8 +- ...20\260\320\275\320\275\321\213\321\205.os" | 84 ++++-- ...20\276\321\201\321\202\320\265\320\271.os" | 8 +- 14 files changed, 310 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index 026b341..ac31af0 100644 --- a/README.md +++ b/README.md @@ -505,7 +505,7 @@ В отличие от МенеджераСущностей, ХранилищеСущностей не требует передачи в методы параметра "ТипСущности". -Хранилища сущностей и пулы сущностей совпадают в рамках одного типа сущности, типа коннектора и строки соединения. Другими словами, два менеджера сущности, инициализированные одним и тем же коннектором и строкой соединения, вернут одинаковые хранилища сущностей одного типа. +Хранилища сущностей и пулы сущностей совпадают в рамках одного типа сущности и одного источника данных. Другими словами, два менеджера сущностей, созданные из одного источника данных, вернут одинаковые хранилища сущностей одного типа. @@ -578,7 +578,7 @@ Поэтому in-memory базу нельзя сочетать с **нулевым размером пула соединений**: при нулевом размере соединение открывается на операцию и закрывается сразу после нее, то есть между операциями открытых соединений не остается. Следующая операция откроет уже пустую базу и получит `no such table`. Переживет база такой промежуток или нет, зависит от внутреннего пулинга провайдера, то есть воспроизводится через раз. -Для in-memory базы используйте ненулевой размер пула — тогда пул придерживает соединения открытыми и база живет, пока жив менеджер сущностей. Если нулевой размер нужен принципиально, держите отдельное соединение к той же базе на все время работы: +Для in-memory базы используйте ненулевой размер пула — тогда пул придерживает соединения открытыми и база живет, пока не закрыт источник данных. Если нулевой размер нужен принципиально, держите отдельное соединение к той же базе на все время работы: ```bsl Держатель = Новый КоннекторSQLite; diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index c7e351d..786a299 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -1,10 +1,13 @@ ## ИсточникДанных -Описание источника данных: имя, тип коннектора, строка соединения, параметры коннектора -и размер пула соединений одним объектом. +Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула +и сам пул соединений одним объектом. -Источник соединений не открывает, только описывает их. Тип коннектора проверяется при -создании: тип без интерфейса `АбстрактныйКоннектор` отвергается с ошибкой. +Пул создается при первом обращении и общий для всех, кто работает через источник. Закрывает +пул сам источник: методом `Закрыть()` или по завершению потока, в котором источник создан, если +автозакрытие не выключено, см. `УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый +источник соединений не выдает. Тип коннектора проверяется при создании: тип без интерфейса +`АбстрактныйКоннектор` отвергается с ошибкой. ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); @@ -84,3 +87,63 @@ // Функция РазмерПула() Экспорт ``` + +### УстановитьАвтоЗакрытие + +```bsl +// Включает или выключает закрытие источника по завершению потока, в котором он создан. +// +// По умолчанию включено. Выключает тот, кто управляет временем жизни источника сам: контейнер +// приложения или код, создающий источник в одном потоке для работы в других. Тогда источник +// закрывается только явным Закрыть(). +// +// Параметры: +// Значение - Булево - Истина - закрывать по завершению потока; Ложь - только явным Закрыть(). +// +Процедура УстановитьАвтоЗакрытие(Знач Значение) Экспорт +``` + +### АвтоЗакрытие + +```bsl +// Признак закрытия источника по завершению потока, см. УстановитьАвтоЗакрытие. +// +// Возвращаемое значение: +// Булево - Истина, если источник закрывается по завершению создавшего его потока. +// +Функция АвтоЗакрытие() Экспорт +``` + +### Закрыть + +```bsl +// Закрывает пул соединений источника: все его соединения закрываются. +// +// Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный +// вызов ничего не делает. +// +Процедура Закрыть() Экспорт +``` + +### Закрыт + +```bsl +// Источник закрыт, см. Закрыть. +// +// Возвращаемое значение: +// Булево - Истина после закрытия, явного или по завершению потока. +// +Функция Закрыт() Экспорт +``` + +### ПулСоединений + +```bsl +// @internal +// Пул соединений источника; создается при первом обращении. +// +// Возвращаемое значение: +// ПулСоединенийСБД - Пул соединений. +// +Функция ПулСоединений() Экспорт +``` diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index b7c1ff7..1ea5541 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -5,9 +5,9 @@ ```bsl // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных: из него берутся тип коннектора, строка соединения, -// параметры коннектора и размер пула. Создание по параметрам соединения устарело и отмечается -// предупреждением в логе, как и параметры, переданные вместе с источником. +// Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного +// источника делят соединения и транзакции потоков. Создание по параметрам соединения устарело и +// отмечается предупреждением в логе, как и параметры, переданные вместе с источником. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. @@ -140,8 +140,10 @@ ### Закрыть ```bsl -// Выполняет очистку полную данных библиотеки. -// Дополнительно посылает всем используемым коннекторам запросы на закрытие соединения. +// Освобождает модель данных менеджера. +// +// Пул соединений и хранилища сущностей принадлежат источнику данных и здесь не закрываются: +// их закрывает ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. // Процедура Закрыть() ``` diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index b122c40..4200e41 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -85,8 +85,9 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), СтрокаСоединения, Неопределено, 0); ``` -Хранилища сущностей работают пулом своего менеджера, поэтому предел общий для менеджера. Пул - -обязательная зависимость хранилища: без менеджера хранилище не создается. +Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника работают +одним пулом, поэтому предел общий для источника. Пул - обязательная зависимость хранилища: без +менеджера хранилище не создается. ### Незавершенные транзакции diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 18c8ae5..e2038fe 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -1,129 +1,118 @@ -// Пул хранилищ сущностей: соответствие типа сущности хранилищу, общее на процесс. -// -// Ключ учитывает еще тип коннектора и строку соединения, поэтому хранилища разных баз не -// смешиваются. Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, -// притом из разных потоков, поэтому попадание в кэш читается под блокировкой чтения: -// сериализуется только создание. -// -#Использовать "../concurrent" - -// Соответствие - Ключ хранилища и структура с хранилищем и признаками его базы. -Перем Хранилища; -// БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. -Перем Блокировка; - -// Возвращает хранилище сущностей, создавая его при первом обращении. -// -// Параметры: -// ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. -// Пул - ПулСоединенийСБД - Пул, которым хранилище выполняет операции. -// -// Возвращаемое значение: -// ХранилищеСущностей - Хранилище для переданной модели. -// -Функция Получить(ОбъектМодели, Пул) Экспорт - - КлючХранилища = ПолучитьКлючХранилища( - ОбъектМодели.ТипСущности(), - Пул.ТипКоннектора(), - Пул.СтрокаСоединения() - ); - - Блокировка.Чтение().Заблокировать(); - ДанныеХранилища = Хранилища.Получить(КлючХранилища); - Блокировка.Чтение().Разблокировать(); - - Если ДанныеХранилища <> Неопределено Тогда - Возврат ДанныеХранилища.Хранилище; - КонецЕсли; - - Блокировка.Запись().Заблокировать(); - - Попытка - - // Пока ждали запись, хранилище мог создать другой поток. - ДанныеХранилища = Хранилища.Получить(КлючХранилища); - Если ДанныеХранилища = Неопределено Тогда - ДанныеХранилища = Новый Структура("Хранилище, ТипКоннектора, СтрокаСоединения"); - ДанныеХранилища.Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Пул); - ДанныеХранилища.ТипКоннектора = Пул.ТипКоннектора(); - ДанныеХранилища.СтрокаСоединения = Пул.СтрокаСоединения(); - Хранилища.Вставить(КлючХранилища, ДанныеХранилища); - КонецЕсли; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - - Возврат ДанныеХранилища.Хранилище; - -КонецФункции - -// Закрывает и убирает из пула хранилища указанной базы. -// -// Параметры: -// ТипКоннектора - Тип - Тип коннектора закрываемой базы. -// СтрокаСоединения - Строка - Строка соединения закрываемой базы. -// -Процедура Закрыть(ТипКоннектора, СтрокаСоединения) Экспорт - - Блокировка.Запись().Заблокировать(); - - Попытка - - ЗакрываемыеХранилища = Новый Массив; - Для Каждого КлючИЗначение Из Хранилища Цикл - ДанныеХранилища = КлючИЗначение.Значение; - - Если ДанныеХранилища.ТипКоннектора = ТипКоннектора - И ДанныеХранилища.СтрокаСоединения = СтрокаСоединения Тогда - - ДанныеХранилища.Хранилище.Закрыть(); - ЗакрываемыеХранилища.Добавить(КлючИЗначение.Ключ); - - КонецЕсли; - КонецЦикла; - - Для Каждого ЗакрываемоеХранилище Из ЗакрываемыеХранилища Цикл - Хранилища.Удалить(ЗакрываемоеХранилище); - КонецЦикла; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - -КонецПроцедуры - -// Составляет ключ хранилища. -// -// Параметры: -// ТипСущности - Тип - Тип сущности. -// ТипКоннектора - Тип - Тип коннектора. -// СтрокаСоединения - Строка - Строка соединения. -// -// Возвращаемое значение: -// Строка - Ключ хранилища. -// -Функция ПолучитьКлючХранилища(ТипСущности, ТипКоннектора, СтрокаСоединения) - Возврат СтрШаблон( - "%1 - %2 - %3", - ТипСущности, - ТипКоннектора, - СтрокаСоединения - ); -КонецФункции - -Хранилища = Новый Соответствие(); -Блокировка = Новый БлокировкаЧтенияЗаписи(); +// Пул хранилищ сущностей: соответствие пула соединений и типа сущности хранилищу, общее на процесс. +// +// Ключ - пул соединений источника данных, поэтому хранилища разных источников не смешиваются, +// а менеджеры одного источника делят хранилища. Обращения идут на каждую прочитанную сущность +// и на каждое разыменование ссылки, притом из разных потоков, поэтому попадание в кэш читается +// под блокировкой чтения: сериализуется только создание. +// +#Использовать "../concurrent" + +// Соответствие - Пул соединений: соответствие типа сущности хранилищу. +Перем Хранилища; +// БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. +Перем Блокировка; + +// Возвращает хранилище сущностей, создавая его при первом обращении. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. +// Пул - ПулСоединенийСБД - Пул, которым хранилище выполняет операции. +// +// Возвращаемое значение: +// ХранилищеСущностей - Хранилище для переданной модели. +// +Функция Получить(ОбъектМодели, Пул) Экспорт + + ТипСущности = ОбъектМодели.ТипСущности(); + + Блокировка.Чтение().Заблокировать(); + Хранилище = НайтиХранилище(Пул, ТипСущности); + Блокировка.Чтение().Разблокировать(); + + Если Хранилище <> Неопределено Тогда + Возврат Хранилище; + КонецЕсли; + + Блокировка.Запись().Заблокировать(); + + Попытка + + // Пока ждали запись, хранилище мог создать другой поток. + Хранилище = НайтиХранилище(Пул, ТипСущности); + Если Хранилище = Неопределено Тогда + ХранилищаПула = Хранилища.Получить(Пул); + Если ХранилищаПула = Неопределено Тогда + ХранилищаПула = Новый Соответствие; + Хранилища.Вставить(Пул, ХранилищаПула); + КонецЕсли; + Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Пул); + ХранилищаПула.Вставить(ТипСущности, Хранилище); + КонецЕсли; + + Исключение + + Блокировка.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + Блокировка.Запись().Разблокировать(); + + Возврат Хранилище; + +КонецФункции + +// Закрывает и убирает из пула хранилища указанного пула соединений. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул соединений закрываемого источника данных. +// +Процедура Закрыть(Пул) Экспорт + + Блокировка.Запись().Заблокировать(); + + Попытка + + ХранилищаПула = Хранилища.Получить(Пул); + Если ХранилищаПула <> Неопределено Тогда + Для Каждого КлючИЗначение Из ХранилищаПула Цикл + КлючИЗначение.Значение.Закрыть(); + КонецЦикла; + Хранилища.Удалить(Пул); + КонецЕсли; + + Исключение + + Блокировка.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + Блокировка.Запись().Разблокировать(); + +КонецПроцедуры + +// Ищет хранилище в пуле. Вызывается при захваченной блокировке. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул соединений. +// ТипСущности - Тип - Тип сущности. +// +// Возвращаемое значение: +// ХранилищеСущностей, Неопределено - Хранилище или Неопределено, если его еще нет. +// +Функция НайтиХранилище(Пул, ТипСущности) + + ХранилищаПула = Хранилища.Получить(Пул); + Если ХранилищаПула = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Возврат ХранилищаПула.Получить(ТипСущности); + +КонецФункции + +Хранилища = Новый Соответствие(); +Блокировка = Новый БлокировкаЧтенияЗаписи(); diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index db3374e..175aecd 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -190,6 +190,8 @@ Попытка Если пПул <> Неопределено И НЕ пЗакрыт Тогда + // Хранилища сущностей держат пул: закрываются вместе с ним. + ХранилищаСущностей.Закрыть(пПул); пПул.Закрыть(); КонецЕсли; пЗакрыт = Истина; diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 5b31354..bb07a94 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -16,9 +16,9 @@ // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных: из него берутся тип коннектора, строка соединения, -// параметры коннектора и размер пула. Создание по параметрам соединения устарело и отмечается -// предупреждением в логе, как и параметры, переданные вместе с источником. +// Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного +// источника делят соединения и транзакции потоков. Создание по параметрам соединения устарело и +// отмечается предупреждением в логе, как и параметры, переданные вместе с источником. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. @@ -273,15 +273,13 @@ КонецПроцедуры -// Освобождает модель данных и хранилища сущностей менеджера. +// Освобождает модель данных менеджера. // -// Пул соединений принадлежит источнику данных и здесь не закрывается: соединения закрывает -// ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. +// Пул соединений и хранилища сущностей принадлежат источнику данных и здесь не закрываются: +// их закрывает ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. // Процедура Закрыть() Экспорт - ХранилищаСущностей.Закрыть(Источник.ТипКоннектора(), Источник.СтрокаСоединения()); - МодельДанных.Очистить(); // Для освобожения ссылок на все коннекторы и соединения с СУБД diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index 852fcf1..af5e5a6 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -9,6 +9,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Хранилище авторов, полученное из менеджера. @@ -37,7 +39,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования доступа коннектором"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -67,6 +72,7 @@ Хранилище = Неопределено; МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест diff --git "a/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 5ba7e73..21da32f 100644 --- "a/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -7,6 +7,8 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -39,7 +41,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования менеджера сущностей"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -85,6 +90,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест diff --git "a/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" "b/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" index 1b686ae..e3ea52a 100644 --- "a/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" +++ "b/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" @@ -12,6 +12,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; @@ -33,7 +35,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования многопоточной работы"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -60,6 +65,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Фоновое задание фиксирует свою транзакцию, а основной поток затем откатывает свою. diff --git "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" index 76f6090..995625d 100644 --- "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" +++ "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" @@ -13,6 +13,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Строка - Строка соединения с базой в памяти. @@ -33,7 +35,10 @@ "СущностьБезГенерируемогоИдентификатора" ); - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); @@ -47,6 +52,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Четыре задания одновременно пишут авторов. Ни одна строка не должна потеряться, и ни один diff --git "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" index 3be5e51..f1ca0c5 100644 --- "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" +++ "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" @@ -18,6 +18,8 @@ // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; &ВключенЕсли("() -> ЗапускатьТестыPostgreSQL()") &ТестовыйНабор @@ -36,7 +38,10 @@ &ПередКаждым Процедура ПередЗапускомТеста() Экспорт - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторPostgreSQL"), СтрокаСоединенияPostgres()); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторPostgreSQL"), СтрокаСоединенияPostgres()); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -60,6 +65,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Четыре задания одновременно пишут авторов. Ни одна строка не должна потеряться, и ни один diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 9cc91e7..942b0c8 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -8,9 +8,9 @@ #Использовать ".." #Использовать "utils" -// ИсточникДанных - Источник, созданный фоновым заданием; главный поток проверяет его после -// завершения задания. -Перем ИсточникФоновогоЗадания; +// ИсточникДанных - Источник, созданный в другом потоке; поток теста проверяет его после +// завершения потока создания. Другой поток дает фоновое задание. +Перем ИсточникДругогоПотока; &Тест Процедура МенеджерыОдногоИсточникаДелятПул() Экспорт @@ -86,11 +86,11 @@ &Тест Процедура ИсточникЗакрываетсяПоЗавершениюПотока() Экспорт - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникВФоне", Новый Массив, Истина); + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникВДругомПотоке", Новый Массив, Истина); Задание.ОжидатьЗавершения(); - Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Фоновое задание прошло без ошибок").ЭтоИстина(); - Ожидаем.Что(ИсточникФоновогоЗадания.Закрыт(), "Источник закрыт по завершению создавшего его потока") + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Источник закрыт по завершению создавшего его потока") .ЭтоИстина(); КонецПроцедуры @@ -98,16 +98,40 @@ &Тест Процедура АвтозакрытиеОтключаетсяСеттером() Экспорт - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтозакрытияВФоне", Новый Массив, Истина); + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтозакрытияВДругомПотоке", Новый Массив, Истина); Задание.ОжидатьЗавершения(); - Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Фоновое задание прошло без ошибок").ЭтоИстина(); - Ожидаем.Что(ИсточникФоновогоЗадания.Закрыт(), "Без автозакрытия источник переживает поток").ЭтоЛожь(); + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Без автозакрытия источник переживает поток").ЭтоЛожь(); - Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); Ожидаем.Что(Менеджер.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); - ИсточникФоновогоЗадания.Закрыть(); + ИсточникДругогоПотока.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ХранилищаНеПереживаютЗакрытиеИсточника() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + + Первый = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "кэш хранилищ"); + ХранилищеПервого = ХранилищеАвторов(Первый); + Первый.Закрыть(); + + Второй = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "кэш хранилищ"); + ХранилищеВторого = ХранилищеАвторов(Второй); + + Ожидаем.Что(ХранилищеПервого = ХранилищеВторого, "Хранилище закрытого источника не переиспользуется") + .ЭтоЛожь(); + Ожидаем.Что(ХранилищеВторого.Получить(), "Хранилище нового источника работает").ИмеетДлину(0); + + Второй.Закрыть(); КонецПроцедуры @@ -125,6 +149,24 @@ КонецПроцедуры +// Хранилище авторов менеджера, созданного из источника. +// +// Параметры: +// Источник - ИсточникДанных - Источник. +// +// Возвращаемое значение: +// ХранилищеСущностей - Хранилище сущностей типа Автор. +// +Функция ХранилищеАвторов(Источник) + + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Возврат Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + +КонецФункции + // Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL // по переменным среды. // @@ -153,25 +195,25 @@ КонецФункции -// Создает источник и открывает его пул из фонового задания. Используется тестом +// Создает источник и открывает его пул в потоке фонового задания. Используется тестом // "ИсточникЗакрываетсяПоЗавершениюПотока". // -Процедура СоздатьИсточникВФоне() Экспорт +Процедура СоздатьИсточникВДругомПотоке() Экспорт - ИсточникФоновогоЗадания = Новый ИсточникДанных("Фон", Тип("КоннекторInMemory"), "фон"); - Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "другой поток"); + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); КонецПроцедуры -// Создает источник без автозакрытия и открывает его пул из фонового задания. Используется тестом -// "АвтозакрытиеОтключаетсяСеттером". +// Создает источник без автозакрытия и открывает его пул в потоке фонового задания. Используется +// тестом "АвтозакрытиеОтключаетсяСеттером". // -Процедура СоздатьИсточникБезАвтозакрытияВФоне() Экспорт +Процедура СоздатьИсточникБезАвтозакрытияВДругомПотоке() Экспорт - ИсточникФоновогоЗадания = Новый ИсточникДанных("Фон", Тип("КоннекторInMemory"), "фон без автозакрытия"); - ИсточникФоновогоЗадания.УстановитьАвтоЗакрытие(Ложь); - Менеджер = Новый МенеджерСущностей(ИсточникФоновогоЗадания); + ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "без автозакрытия"); + ИсточникДругогоПотока.УстановитьАвтоЗакрытие(Ложь); + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); КонецПроцедуры diff --git "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index b57f724..0e607b2 100644 --- "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -12,6 +12,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Хранилище авторов, полученное из менеджера. @@ -39,7 +41,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования хранилища сущностей"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -69,6 +74,7 @@ Хранилище = Неопределено; МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест From c841bd428671f557cbc4c0d785f0d6dbeb8fd896 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 20:43:06 +0000 Subject: [PATCH 03/55] =?UTF-8?q?=D0=92=D0=BD=D1=83=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=B5=D0=B5=20API=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=98?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=94=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85,=20=D0=B0=20=D0=BD=D0=B5=20=D1=87=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=20=D0=BF=D1=83=D0=BB=20=D1=81=D0=BE=D0=B5?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit РаботаСКоннекторами принимает источник: операции Сохранить, Получить, ПолучитьОдно, Удалить, ВыполнитьСКоннектором, ВычислитьСКоннектором и транзакции потока берут пул у источника сами. Обертки *ЧерезПул слиты с телами операций: второго набора функций с коннектором в модуле больше нет. Кэш хранилищ ключуется источником, хранилище хранит источник, а ИсточникДанных.Закрыть() закрывает хранилища по себе. У менеджера вместо служебного ПолучитьПулСоединений() появился ИсточникДанных(); пул тесты берут через источник. Тестовый коннектор пула дополнен методами интерфейса АбстрактныйКоннектор: источник проверяет интерфейс при создании. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\276\321\201\321\202\320\265\320\271.md" | 15 + ...20\276\321\201\321\202\320\265\320\271.md" | 11 +- ...20\276\321\200\320\260\320\274\320\270.os" | 277 +++++++++--------- ...20\276\321\201\321\202\320\265\320\271.os" | 56 ++-- ...20\260\320\275\320\275\321\213\321\205.os" | 4 +- ...20\276\321\201\321\202\320\265\320\271.os" | 55 ++-- ...20\276\321\201\321\202\320\265\320\271.os" | 32 +- ...21\200\321\213\321\202\320\270\320\265.os" | 28 ++ ...21\207\320\275\320\270\320\272\320\260.os" | 149 ++++++++++ ...21\202\320\276\321\200\320\276\320\274.os" | 10 +- ...20\260\320\275\320\275\321\213\321\205.os" | 4 +- ...20\260\320\275\320\275\321\213\321\205.os" | 14 +- ...20\270\320\271\320\241\320\221\320\224.os" | 24 +- ...20\276\321\201\321\202\320\265\320\271.os" | 6 +- 14 files changed, 441 insertions(+), 244 deletions(-) create mode 100644 "tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 1ea5541..c3bce77 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -212,6 +212,21 @@ Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Знач Объект = Неопределено) ``` +### ИсточникДанных + +```bsl +// Источник данных, из которого создан менеджер. +// +// Все обращения менеджера к БД идут соединениями этого источника. Хранилища сущностей и все +// менеджеры одного источника делят его пул: предел одновременно живущих соединений общий, +// а транзакция потока видна из каждого из них. +// +// Возвращаемое значение: +// ИсточникДанных - Источник данных менеджера. +// +Функция ИсточникДанных() Экспорт +``` + ### ПолучитьХранилищеСущностей ```bsl diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index d1b08ac..ddc06ce 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -3,8 +3,15 @@ ### ПриСозданииОбъекта ```bsl - -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ТипКоннектора, Знач СтрокаСоединения, ПараметрыКоннектора) +// Создает хранилище, привязанное к типу сущности и работающее переданным источником данных. +// +// Создается пулом хранилищ сущностей, напрямую конструировать не следует. +// +// Параметры: +// ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. +// ПИсточник - ИсточникДанных - Источник, соединениями которого выполняются операции. +// +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПИсточник) ``` ### СоздатьЭлемент diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 1fbfdd6..3ad0c34 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -17,20 +17,27 @@ КонецЕсли; КонецПроцедуры -// Сохраняет сущность в БД соединением из пула. +// Сохраняет сущность в БД соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Сохраняемая сущность. // -Процедура СохранитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность) Экспорт + ПроверитьЧтоКлассЯвляетсяСущностью(АктивнаяЗапись.ТипСущности(Сущность)); + ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); + ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); + + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка - Сохранить(Соединение.ПолучитьКоннектор(), ОбъектМодели, ПулСущностей, Сущность); + Коннектор = Соединение.ПолучитьКоннектор(); + Коннектор.Сохранить(ОбъектМодели, Сущность); + СохранитьПодчиненныеТаблицы(Коннектор, ОбъектМодели, Сущность); Исключение Пул.Освободить(); ВызватьИсключение; @@ -38,25 +45,38 @@ Пул.Освободить(); + ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); + КонецПроцедуры -// Ищет сущности соединением из пула. +// Ищет сущности соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// ОпцииПоиска - ОпцииПоиска - Опции поиска. +// ОпцииПоиска - Соответствие, ОпцииПоиска - Опции поиска. // // Возвращаемое значение: // Массив - Найденные сущности. // -Функция ПолучитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт + + ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ОпцииПоиска); + НайденныеСущности = Новый Массив; + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка - Результат = Получить(Соединение.ПолучитьКоннектор(), Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска); + + Коннектор = Соединение.ПолучитьКоннектор(); + НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); + + Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл + НайденныеСущности.Добавить(СобратьСущность(Коннектор, Источник, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); + КонецЦикла; + Исключение Пул.Освободить(); ВызватьИсключение; @@ -64,53 +84,59 @@ Пул.Освободить(); - Возврат Результат; + Возврат НайденныеСущности; КонецФункции -// Ищет одну сущность соединением из пула. +// Ищет одну сущность соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// ОпцииПоиска - ОпцииПоиска - Опции поиска. +// ОпцииПоиска - Соответствие, ОпцииПоиска, Произвольный - Опции поиска; любой другой тип - идентификатор. // // Возвращаемое значение: // Произвольный - Найденная сущность. // Неопределено - Сущность не найдена. // -Функция ПолучитьОдноЧерезПул(Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - Соединение = Пул.Занять(); - - Попытка - Результат = ПолучитьОдно(Соединение.ПолучитьКоннектор(), Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска); - Исключение - Пул.Освободить(); - ВызватьИсключение; - КонецПопытки; + Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") Тогда + ПередаваемыеОпцииПоиска = ОпцииПоиска; + ИначеЕсли ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда + ПередаваемыеОпцииПоиска = ОпцииПоиска; + Иначе + ПередаваемыеОпцииПоиска = Новый ОпцииПоиска(); + ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); + КонецЕсли; - Пул.Освободить(); + НайденныеСущности = Получить(Источник, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); - Возврат Результат; + Если НайденныеСущности.Количество() = 0 Тогда + Возврат Неопределено; + Иначе + Возврат НайденныеСущности[0]; + КонецЕсли; КонецФункции -// Удаляет сущность из БД соединением из пула. +// Удаляет сущность из БД соединением источника данных. +// Сущность должна иметь заполненный идентификатор. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Удаляемая сущность. // -Процедура УдалитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность) Экспорт + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка - Удалить(Соединение.ПолучитьКоннектор(), ОбъектМодели, ПулСущностей, Сущность); + Соединение.ПолучитьКоннектор().Удалить(ОбъектМодели, Сущность); Исключение Пул.Освободить(); ВызватьИсключение; @@ -118,23 +144,27 @@ Пул.Освободить(); + // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы ничего. + ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); + КонецПроцедуры -// Выполняет алгоритм коннектором из пула и возвращает соединение обратно. +// Выполняет алгоритм коннектором источника данных и возвращает соединение обратно. // // Соединение занимается на время выполнения алгоритма. Если текущий поток исполнения уже // начал транзакцию, алгоритм получает коннектор этой транзакции и попадает в нее. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется алгоритм. // Алгоритм - Строка - Лямбда-выражение с одним параметром - коннектором. // Контекст - Структура - Значения, доступные в алгоритме по именам ключей. // Объект - Сценарий - Объект, поля и методы которого доступны в алгоритме. // -Процедура ВыполнитьСКоннекторомЧерезПул(Пул, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт +Процедура ВыполнитьСКоннектором(Источник, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниПроцедура()); + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -148,13 +178,14 @@ КонецПроцедуры -// Вычисляет значение алгоритмом, выполненным коннектором из пула, и возвращает соединение обратно. +// Вычисляет значение алгоритмом, выполненным коннектором источника данных, и возвращает +// соединение обратно. // // Соединение занимается на время выполнения алгоритма. Если текущий поток исполнения уже // начал транзакцию, алгоритм получает коннектор этой транзакции и попадает в нее. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется алгоритм. // Алгоритм - Строка - Лямбда-выражение с одним параметром - коннектором. // Контекст - Структура - Значения, доступные в алгоритме по именам ключей. // Объект - Сценарий - Объект, поля и методы которого доступны в алгоритме. @@ -162,10 +193,11 @@ // Возвращаемое значение: // Произвольный - Результат алгоритма. // -Функция ВычислитьСКоннекторомЧерезПул(Пул, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт +Функция ВычислитьСКоннектором(Источник, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниФункция()); + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -214,10 +246,11 @@ // попадают в эту транзакцию, а завершить ее другой поток не может. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого открывается транзакция. // -Процедура НачатьТранзакциюПотокаИсполнения(Пул) Экспорт +Процедура НачатьТранзакциюПотокаИсполнения(Источник) Экспорт + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -236,19 +269,19 @@ // Фиксирует транзакцию текущего потока исполнения и возвращает соединение в пул. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // -Процедура ЗафиксироватьТранзакциюПотокаИсполнения(Пул) Экспорт - ЗавершитьТранзакциюПотокаИсполнения(Пул, Истина); +Процедура ЗафиксироватьТранзакциюПотокаИсполнения(Источник) Экспорт + ЗавершитьТранзакциюПотокаИсполнения(Источник, Истина); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает соединение в пул. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // -Процедура ОтменитьТранзакциюПотокаИсполнения(Пул) Экспорт - ЗавершитьТранзакциюПотокаИсполнения(Пул, Ложь); +Процедура ОтменитьТранзакциюПотокаИсполнения(Источник) Экспорт + ЗавершитьТранзакциюПотокаИсполнения(Источник, Ложь); КонецПроцедуры // Завершает транзакцию текущего потока и освобождает его соединение. @@ -262,11 +295,12 @@ // тем более незачем. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // Фиксировать - Булево - Истина - зафиксировать транзакцию, Ложь - отменить. // -Процедура ЗавершитьТранзакциюПотокаИсполнения(Пул, Знач Фиксировать) +Процедура ЗавершитьТранзакциюПотокаИсполнения(Источник, Знач Фиксировать) + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -284,76 +318,75 @@ КонецПроцедуры -Процедура Сохранить(Коннектор, ОбъектМодели, ПулСущностей, Сущность) Экспорт - ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); +// Перезаписывает строки подчиненных таблиц сущности: старые строки удаляются, текущее +// содержимое коллекций сохраняется заново. +// +// Параметры: +// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// Сущность - Произвольный - Сохраняемая сущность. +// +Процедура СохранитьПодчиненныеТаблицы(Коннектор, ОбъектМодели, Сущность) + + Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл - ПроверитьЧтоКлассЯвляетсяСущностью(ТипСущности); - ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); - ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); - - Коннектор.Сохранить(ОбъектМодели, Сущность); - - // Сохранение полей, расширяемых таблицами - ПодчиненныеТаблицы = ОбъектМодели.ПодчиненныеТаблицы(); - Для Каждого ПодчиненнаяТаблица Из ПодчиненныеТаблицы Цикл - ОбъектМоделиЭлементКоллекции = ОбработкаКоллекций.ПолучитьОбъектМоделиДляПодчиненнойТаблицы( ОбъектМодели, ПодчиненнаяТаблица ); - + // Очистка подчиненной таблицы ОпцииПоиска = Новый ОпцииПоиска(); ОпцииПоиска.Отбор("ref", ВидСравнения.Равно, ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); Коннектор.УдалитьСтрокиВТаблице(ОбъектМоделиЭлементКоллекции, ОпцииПоиска); - + РасширяемоеПоле = ОбъектМодели.ПолучитьЗначениеПоля(Сущность, ПодчиненнаяТаблица.ИмяПоля); - + Если НЕ ЗначениеЗаполнено(РасширяемоеПоле) Тогда Продолжить; КонецЕсли; - + КоллекцияСущностей = ОбработкаКоллекций.ПреобразоватьКоллекциюККоллекцииСущностей( РасширяемоеПоле, Сущность, ОбъектМодели, ПодчиненнаяТаблица ); - + Для Каждого ЭлементКоллекции Из КоллекцияСущностей Цикл Коннектор.Сохранить(ОбъектМоделиЭлементКоллекции, ЭлементКоллекции); КонецЦикла; - + КонецЦикла; - ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); КонецПроцедуры -Функция Получить(Коннектор, Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - - Колонки = ОбъектМодели.Колонки(); - ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(Колонки, ОпцииПоиска); - - НайденныеСущности = Новый Массив; - НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); - - Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл - - ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( - НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), - ОбъектМодели.Идентификатор().ИмяПоля - ); - - Сущность = ПолучитьСущностьИзПула(Пул, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); +// Собирает сущность по прочитанной строке таблицы: берет экземпляр из пула сущностей, +// заполняет колонки и подчиненные таблицы. +// +// Параметры: +// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. +// Источник - ИсточникДанных - Источник, соединением которого читаются ссылки и коллекции. +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. +// +// Возвращаемое значение: +// Произвольный - Заполненная сущность. +// +Функция СобратьСущность(Коннектор, Источник, ОбъектМодели, ПулСущностей, НайденнаяСтрока) - ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Пул, ОбъектМодели, ЗначениеИдентификатора); - ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Пул, ОбъектМодели, ЗначениеИдентификатора); + ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( + НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), + ОбъектМодели.Идентификатор().ИмяПоля + ); - НайденныеСущности.Добавить(Сущность); + Сущность = ПолучитьСущностьИзПула(Источник, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); - КонецЦикла; + ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Источник, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Источник, ОбъектМодели, ЗначениеИдентификатора); - Возврат НайденныеСущности; + Возврат Сущность; КонецФункции @@ -426,7 +459,7 @@ // Возвращает экземпляр сущности из пула сущностей, создавая его при первом обращении. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит хранилище сущности. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ЗначениеИдентификатора - Произвольный - Идентификатор сущности. @@ -434,14 +467,14 @@ // Возвращаемое значение: // Произвольный - Экземпляр сущности. // -Функция ПолучитьСущностьИзПула(Пул, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) +Функция ПолучитьСущностьИзПула(Источник, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) Сущность = ПулСущностей.ПолучитьИлиУмолчание(ЗначениеИдентификатора, Неопределено); Если Сущность <> Неопределено Тогда Возврат Сущность; КонецЕсли; - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Пул); + ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Источник); Сущность = АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей); // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться @@ -458,11 +491,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Источник - ИсточникДанных - Источник, соединением которого читаются ссылки. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Пул, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Источник, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого Колонка Из ОбъектМодели.Колонки() Цикл @@ -472,7 +505,7 @@ ЗначениеКолонки = РазыменоватьСсылку( ЗначениеКолонки, Колонка.ТипСсылки, - Пул, + Источник, ОбъектМодели, Сущность, ЗначениеИдентификатора @@ -493,7 +526,7 @@ // Параметры: // ЗначениеКолонки - Произвольный - Идентификатор сущности, на которую указывает ссылка. // ТипСсылки - Тип - Тип сущности, на которую указывает ссылка. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит хранилище сущности по ссылке. // ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит колонка. // Сущность - Произвольный - Сущность, которой принадлежит колонка. // ЗначениеИдентификатора - Произвольный - Идентификатор этой сущности. @@ -504,7 +537,7 @@ Функция РазыменоватьСсылку( Знач ЗначениеКолонки, Знач ТипСсылки, - Пул, + Источник, ОбъектМодели, Сущность, Знач ЗначениеИдентификатора @@ -514,7 +547,7 @@ Возврат Сущность; КонецЕсли; - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипСсылки), Пул); + ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипСсылки), Источник); Возврат ХранилищеСущностейСсылки.ПолучитьОдно(ЗначениеКолонки); @@ -525,11 +558,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Источник - ИсточникДанных - Источник, которому принадлежат хранилища элементов коллекций. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Пул, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Источник, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл @@ -545,7 +578,7 @@ ЗначениеКолонки = ОбработкаКоллекций.ПреобразоватьКоллекциюСтрокККоллекции(Строки, ПодчиненнаяТаблица); Если ПодчиненнаяТаблица.КаскадноеЧтение И ТипыКолонок.ЭтоСсылочныйТип(ПодчиненнаяТаблица.ТипЭлемента) Тогда - ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Пул, ОбъектМодели); + ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Источник, ОбъектМодели); КонецЕсли; ОбъектМодели.УстановитьЗначениеПодчиненнойТаблицыВПоле(Сущность, ПодчиненнаяТаблица.ИмяПоля, ЗначениеКолонки); @@ -559,12 +592,12 @@ // Параметры: // Коллекция - Массив - Коллекция идентификаторов, изменяется на месте. // ТипЭлемента - Тип - Тип сущности элементов коллекции. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит хранилище элементов. // ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит коллекция. // -Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Пул, ОбъектМодели) +Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Источник, ОбъектМодели) - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипЭлемента), Пул); + ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипЭлемента), Источник); Для НомерИндекса = 0 По Коллекция.ВГраница() Цикл Коллекция[НомерИндекса] = ХранилищеСущностейСсылки.ПолучитьОдно(Коллекция[НомерИндекса]); @@ -572,42 +605,6 @@ КонецПроцедуры -Функция ПолучитьОдно(Коннектор, Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - - Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") Тогда - ПередаваемыеОпцииПоиска = ОпцииПоиска; - ИначеЕсли ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда - ПередаваемыеОпцииПоиска = ОпцииПоиска; - Иначе - ПередаваемыеОпцииПоиска = Новый ОпцииПоиска(); - ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); - КонецЕсли; - - НайденныеСущности = Получить(Коннектор, Пул, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); - - Если НайденныеСущности.Количество() = 0 Тогда - Возврат Неопределено; - Иначе - Возврат НайденныеСущности[0]; - КонецЕсли; - -КонецФункции - -// Удаляет удаление сущности из базы данных. -// Сущность должна иметь заполненный идентификатор. -// -// Параметры: -// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// Сущность - Произвольный - Удаляемая сущность -// -Процедура Удалить(Коннектор, ОбъектМодели, ПулСущностей, Сущность) Экспорт - Коннектор.Удалить(ОбъектМодели, Сущность); - // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы ничего. - ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); -КонецПроцедуры - // Посылает коннектору запрос на начало транзакции. // // Параметры: @@ -635,20 +632,20 @@ Коннектор.ОтменитьТранзакцию(); КонецПроцедуры -// <Описание процедуры> +// Проверяет, что тип класса размечен как сущность. // // Параметры: // ТипКласса - Тип - Тип, в котором проверяется наличие необходимых аннотаций. // Процедура ПроверитьЧтоКлассЯвляетсяСущностью(ТипКласса) - + РефлекторОбъекта = Новый РефлекторОбъекта(ТипКласса); ТаблицаМетодов = РефлекторОбъекта.ПолучитьТаблицуМетодов("Сущность", Ложь); Ожидаем.Что(ТаблицаМетодов, СтрШаблон("Класс %1 не имеет аннотации &Сущность", ТипКласса)).ИмеетДлину(1); - + ТаблицаСвойств = РефлекторОбъекта.ПолучитьТаблицуСвойств("Идентификатор"); Ожидаем.Что(ТаблицаСвойств, СтрШаблон("Класс %1 не имеет поля с аннотацией &Идентификатор", ТипКласса)).ИмеетДлину(1); - + КонецПроцедуры Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) @@ -660,7 +657,7 @@ Если ОбъектМодели.Идентификатор().ГенерируемоеЗначение Тогда Возврат; КонецЕсли; - + ЗначениеИдентификатора = ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность); Ожидаем.Что( ЗначениеИдентификатора, СтрШаблон("Сущность с типом %1 должна иметь заполненный идентификатор", Тип(Сущность)) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index e2038fe..8e00414 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -1,13 +1,13 @@ -// Пул хранилищ сущностей: соответствие пула соединений и типа сущности хранилищу, общее на процесс. +// Пул хранилищ сущностей: соответствие источника данных и типа сущности хранилищу, общее на процесс. // -// Ключ - пул соединений источника данных, поэтому хранилища разных источников не смешиваются, -// а менеджеры одного источника делят хранилища. Обращения идут на каждую прочитанную сущность -// и на каждое разыменование ссылки, притом из разных потоков, поэтому попадание в кэш читается -// под блокировкой чтения: сериализуется только создание. +// Ключ - источник данных, поэтому хранилища разных источников не смешиваются, а менеджеры одного +// источника делят хранилища. Обращения идут на каждую прочитанную сущность и на каждое +// разыменование ссылки, притом из разных потоков, поэтому попадание в кэш читается под +// блокировкой чтения: сериализуется только создание. // #Использовать "../concurrent" -// Соответствие - Пул соединений: соответствие типа сущности хранилищу. +// Соответствие - Источник данных: соответствие типа сущности хранилищу. Перем Хранилища; // БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. Перем Блокировка; @@ -16,17 +16,17 @@ // // Параметры: // ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. -// Пул - ПулСоединенийСБД - Пул, которым хранилище выполняет операции. +// Источник - ИсточникДанных - Источник, соединениями которого хранилище выполняет операции. // // Возвращаемое значение: // ХранилищеСущностей - Хранилище для переданной модели. // -Функция Получить(ОбъектМодели, Пул) Экспорт +Функция Получить(ОбъектМодели, Источник) Экспорт ТипСущности = ОбъектМодели.ТипСущности(); Блокировка.Чтение().Заблокировать(); - Хранилище = НайтиХранилище(Пул, ТипСущности); + Хранилище = НайтиХранилище(Источник, ТипСущности); Блокировка.Чтение().Разблокировать(); Если Хранилище <> Неопределено Тогда @@ -38,15 +38,15 @@ Попытка // Пока ждали запись, хранилище мог создать другой поток. - Хранилище = НайтиХранилище(Пул, ТипСущности); + Хранилище = НайтиХранилище(Источник, ТипСущности); Если Хранилище = Неопределено Тогда - ХранилищаПула = Хранилища.Получить(Пул); - Если ХранилищаПула = Неопределено Тогда - ХранилищаПула = Новый Соответствие; - Хранилища.Вставить(Пул, ХранилищаПула); + ХранилищаИсточника = Хранилища.Получить(Источник); + Если ХранилищаИсточника = Неопределено Тогда + ХранилищаИсточника = Новый Соответствие; + Хранилища.Вставить(Источник, ХранилищаИсточника); КонецЕсли; - Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Пул); - ХранилищаПула.Вставить(ТипСущности, Хранилище); + Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Источник); + ХранилищаИсточника.Вставить(ТипСущности, Хранилище); КонецЕсли; Исключение @@ -63,23 +63,23 @@ КонецФункции -// Закрывает и убирает из пула хранилища указанного пула соединений. +// Закрывает и убирает из пула хранилища указанного источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул соединений закрываемого источника данных. +// Источник - ИсточникДанных - Закрываемый источник данных. // -Процедура Закрыть(Пул) Экспорт +Процедура Закрыть(Источник) Экспорт Блокировка.Запись().Заблокировать(); Попытка - ХранилищаПула = Хранилища.Получить(Пул); - Если ХранилищаПула <> Неопределено Тогда - Для Каждого КлючИЗначение Из ХранилищаПула Цикл + ХранилищаИсточника = Хранилища.Получить(Источник); + Если ХранилищаИсточника <> Неопределено Тогда + Для Каждого КлючИЗначение Из ХранилищаИсточника Цикл КлючИЗначение.Значение.Закрыть(); КонецЦикла; - Хранилища.Удалить(Пул); + Хранилища.Удалить(Источник); КонецЕсли; Исключение @@ -97,20 +97,20 @@ // Ищет хранилище в пуле. Вызывается при захваченной блокировке. // // Параметры: -// Пул - ПулСоединенийСБД - Пул соединений. +// Источник - ИсточникДанных - Источник данных. // ТипСущности - Тип - Тип сущности. // // Возвращаемое значение: // ХранилищеСущностей, Неопределено - Хранилище или Неопределено, если его еще нет. // -Функция НайтиХранилище(Пул, ТипСущности) +Функция НайтиХранилище(Источник, ТипСущности) - ХранилищаПула = Хранилища.Получить(Пул); - Если ХранилищаПула = Неопределено Тогда + ХранилищаИсточника = Хранилища.Получить(Источник); + Если ХранилищаИсточника = Неопределено Тогда Возврат Неопределено; КонецЕсли; - Возврат ХранилищаПула.Получить(ТипСущности); + Возврат ХранилищаИсточника.Получить(ТипСущности); КонецФункции diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 175aecd..166f9c2 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -190,8 +190,8 @@ Попытка Если пПул <> Неопределено И НЕ пЗакрыт Тогда - // Хранилища сущностей держат пул: закрываются вместе с ним. - ХранилищаСущностей.Закрыть(пПул); + // Хранилища сущностей работают источником: закрываются вместе с ним. + ХранилищаСущностей.Закрыть(ЭтотОбъект); пПул.Закрыть(); КонецЕсли; пЗакрыт = Истина; diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index bb07a94..fbd2e8c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -66,16 +66,6 @@ КонецПроцедуры -// Пул соединений источника данных. Через него идут все обращения менеджера к БД; создает пул -// источник при первом обращении, закрывает тоже он. -// -// Возвращаемое значение: -// ПулСоединенийСБД - Пул соединений источника. -// -Функция Пул() - Возврат Источник.ПулСоединений(); -КонецФункции - // Предупреждает, что вместе с источником данных переданы устаревшие параметры конструктора. // // Параметры: @@ -131,16 +121,17 @@ // Процедура Инициализировать() Экспорт - Соединение = Пул().Занять(); + Пул = Источник.ПулСоединений(); + Соединение = Пул.Занять(); Попытка СоздатьТаблицы(Соединение.ПолучитьКоннектор()); Исключение - Пул().Освободить(); + Пул.Освободить(); ВызватьИсключение; КонецПопытки; - Пул().Освободить(); + Пул.Освободить(); КонецПроцедуры @@ -197,7 +188,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.СохранитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(Источник,ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -224,7 +215,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(Источник,ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -252,7 +243,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(Пул(),ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(Источник,ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -269,7 +260,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.УдалитьЧерезПул(Пул(),ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(Источник,ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -293,19 +284,19 @@ // попадают в эту транзакцию, а завершить ее другое фоновое задание не может. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Пул()); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Пул()); + РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Пул()); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -326,7 +317,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(Пул(),Алгоритм, Контекст, Объект); + РаботаСКоннекторами.ВыполнитьСКоннектором(Источник,Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -348,24 +339,20 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(Пул(),Алгоритм, Контекст, Объект); + Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник,Алгоритм, Контекст, Объект); КонецФункции -// BSLLS:UsingServiceTag-off -// @internal -// BSLLS:UsingServiceTag-on -// Для служебного пользования. -// -// Возвращает пул соединений источника данных, из которого создан менеджер. +// Источник данных, из которого создан менеджер. // -// Хранилища сущностей и все менеджеры одного источника работают одним пулом: предел -// одновременно живущих соединений общий, а транзакция потока видна из каждого из них. +// Все обращения менеджера к БД идут соединениями этого источника. Хранилища сущностей и все +// менеджеры одного источника делят его пул: предел одновременно живущих соединений общий, +// а транзакция потока видна из каждого из них. // // Возвращаемое значение: -// ПулСоединенийСБД - Пул соединений источника. +// ИсточникДанных - Источник данных менеджера. // -Функция ПолучитьПулСоединений() Экспорт - Возврат Пул(); +Функция ИсточникДанных() Экспорт + Возврат Источник; КонецФункции // Получает ХранилищеСущностей, привязанное к переданному типу сущности. @@ -378,7 +365,7 @@ // Функция ПолучитьХранилищеСущностей(ТипСущности) Экспорт ОбъектМодели = МодельДанных.Получить(ТипСущности); - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Пул()); + ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Источник); Возврат ХранилищеСущностей; КонецФункции diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index cea39a8..c2e0885 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -9,22 +9,22 @@ // Карта разделяется всеми потоками исполнения, поэтому синхронизированная. Перем ПулСущностей; -// ПулСоединенийСБД - Пул, из которого берется коннектор на каждую операцию. Общий с менеджером сущностей. -Перем ПулСоединений; +// ИсточникДанных - Источник, соединением которого выполняется каждая операция. +Перем Источник; -// Создает хранилище, привязанное к типу сущности и работающее переданным пулом. +// Создает хранилище, привязанное к типу сущности и работающее переданным источником данных. // // Создается пулом хранилищ сущностей, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ППулСоединений - ПулСоединенийСБД - Пул, которым выполняются операции. +// ПИсточник - ИсточникДанных - Источник, соединениями которого выполняются операции. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ППулСоединений) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПИсточник) ОбъектМодели = ПОбъектМодели; ПулСущностей = Новый СинхронизированнаяКарта(Новый КартаСоответствие()); - ПулСоединений = ППулСоединений; + Источник = ПИсточник; КонецПроцедуры @@ -47,7 +47,7 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для сохранения в БД. // Процедура Сохранить(Сущность) Экспорт - РаботаСКоннекторами.СохранитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры // Осуществляет поиск сущностей типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -72,7 +72,7 @@ ОпцииПоиска = Новый ОпцииПоиска(); КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -99,7 +99,7 @@ ОпцииПоиска = Новый ОпцииПоиска(); КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -113,12 +113,12 @@ // Сущность - Произвольный - Удаляемая сущность // Процедура Удалить(Сущность) Экспорт - РаботаСКоннекторами.УдалитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры // Выполняет очистку текущего Хранилища сущностей. // -// Коннекторов хранилище не держит: они живут в пуле, и закрывает их менеджер сущностей. +// Коннекторов хранилище не держит: они живут в пуле источника данных, и закрывает их источник. // Процедура Закрыть() Экспорт ПулСущностей.Очистить(); @@ -131,19 +131,19 @@ // сам менеджер сущностей. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(ПулСоединений); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -164,7 +164,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + РаботаСКоннекторами.ВыполнитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -186,7 +186,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецФункции // Возвращает объект модели, связанный с текущим хранилищем сущностей. diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index 1f4e03d..da731b9 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -83,3 +83,31 @@ Функция Открыт() Экспорт Возврат Открыт; КонецФункции + +// Операции над данными коннектор не поддерживает: он изображает только соединение. Методы +// объявлены, чтобы коннектор отвечал интерфейсу АбстрактныйКоннектор, который проверяет +// ИсточникДанных. +// +// BSLLS:UnusedParameters-off + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +// BSLLS:UnusedParameters-on diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" new file mode 100644 index 0000000..07ab30f --- /dev/null +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -0,0 +1,149 @@ +// Внутреннее API библиотеки работает через источник данных, а не через его пул соединений. +// +// Пул остается деталью источника: с ним напрямую работают только методы, занимающие и +// освобождающие соединения. Кэш хранилищ и операции над сущностями получают источник. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off + +#Использовать ".." +#Использовать "../src/internal" +#Использовать "utils" + +&Тест +Процедура КэшХранилищВыдаетХранилищеПоИсточнику() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище по источнику"); + Менеджер = МенеджерАвторов(Источник); + ОбъектМодели = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")); + + Хранилище = ХранилищаСущностей.Получить(ОбъектМодели, Источник); + + Ожидаем.Что(Хранилище.Получить(), "Хранилище, полученное по источнику, работает").ИмеетДлину(0); + Ожидаем.Что(Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Менеджер выдает то же хранилище") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура КэшХранилищЗакрываетсяПоИсточнику() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие по источнику"); + Менеджер = МенеджерАвторов(Источник); + ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + ХранилищаСущностей.Закрыть(Источник); + + Ожидаем.Что(ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Хранилище создано заново") + .ЭтоЛожь(); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура АлгоритмВыполняетсяКоннекторомЧерезИсточник() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "алгоритм через источник"); + + ТипКоннектора = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> ТипЗнч(Коннектор)"); + + Ожидаем.Что(ТипКоннектора, "Алгоритм получил коннектор источника").Равно(Тип("КоннекторInMemory")); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура СущностьСохраняетсяИЧитаетсяЧерезИсточник() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "операции через источник"); + Менеджер = МенеджерАвторов(Источник); + ОбъектМодели = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")); + ПулСущностей = Менеджер.ПолучитьПулСущностей(Тип("Автор")); + + Автор = Новый Автор; + Автор.Имя = "Иван"; + Автор.ВтороеИмя = "Иванов"; + РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Автор); + + Авторы = РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, Новый ОпцииПоиска); + + Ожидаем.Что(Авторы, "Сохраненный автор прочитан через источник").ИмеетДлину(1); + + Источник.Закрыть(); + +КонецПроцедуры + +// Транзакции нужен коннектор СУБД: КоннекторInMemory их не поддерживает. +&Тест +Процедура ТранзакцияПотокаОткрываетсяЧерезИсточник() Экспорт + + Источник = ИсточникСТранзакциями(); + + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); + Первый = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> Коннектор"); + Второй = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> Коннектор"); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); + + Ожидаем.Что(Первый = Второй, "Операции потока попадают на коннектор транзакции").ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Менеджер с типом Автор в модели, созданный из источника. +// +// Параметры: +// Источник - ИсточникДанных - Источник. +// +// Возвращаемое значение: +// МенеджерСущностей - Менеджер с инициализированной моделью. +// +Функция МенеджерАвторов(Источник) + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + + Менеджер = Новый МенеджерСущностей(Источник); + // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Возврат Менеджер; + +КонецФункции + +// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL +// по переменным среды. +// +// Возвращаемое значение: +// ИсточникДанных - Источник. +// +Функция ИсточникСТранзакциями() + + Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); + КонецЕсли; + + Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда + СтрокаСоединения = СтрШаблон( + "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), + ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") + ); + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + КонецЕсли; + + ВызватьИсключение "Нет доступного коннектора с транзакциями"; + +КонецФункции diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index af5e5a6..d0ce547 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -166,7 +166,7 @@ .ВыбрасываетИсключение(); Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -183,7 +183,7 @@ .ВыбрасываетИсключение(); Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -229,7 +229,7 @@ КонецПопытки; Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -280,7 +280,7 @@ ПараметрыКоннектора.Добавить("ЛишнийПараметр"); Попытка - ПараметрыПула = Менеджер.ПолучитьПулСоединений().ПараметрыКоннектора(); + ПараметрыПула = Менеджер.ИсточникДанных().ПулСоединений().ПараметрыКоннектора(); Ожидаем.Что(ПараметрыПула, "Параметры дошли до пула").ИмеетДлину(1); Ожидаем.Что(ПараметрыПула[0]).Равно("ПроизвольныйПараметр"); Исключение @@ -297,7 +297,7 @@ &Тест Процедура СоединениеЗнаетОСвоейТранзакции() Экспорт - Пул = МенеджерСущностей.ПолучитьПулСоединений(); + Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); МенеджерСущностей.НачатьТранзакцию(); diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index afbc7ec..ecd14bc 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -72,7 +72,7 @@ МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.Инициализировать(); - Пул = МенеджерСущностей.ПолучитьПулСоединений(); + Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); Ожидаем.Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); Ожидаем.Что(Пул.СтрокаСоединения()).Равно("источник"); Ожидаем.Что(Пул.ПараметрыКоннектора()).Равно(Источник.ПараметрыКоннектора()); @@ -93,7 +93,7 @@ ЧужиеПараметры.Добавить("из конструктора"); МенеджерСущностей = Новый МенеджерСущностей(Источник, "другая строка", ЧужиеПараметры, 7); - Пул = МенеджерСущностей.ПолучитьПулСоединений(); + Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); Ожидаем.Что(Пул.СтрокаСоединения(), "Строка соединения взята из источника").Равно("источник"); Ожидаем.Что(Пул.ПараметрыКоннектора(), "Параметры коннектора взяты из источника") .Равно(Источник.ПараметрыКоннектора()); diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 942b0c8..6154586 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -19,13 +19,25 @@ Первый = Новый МенеджерСущностей(Источник); Второй = Новый МенеджерСущностей(Источник); - Ожидаем.Что(Первый.ПолучитьПулСоединений() = Второй.ПолучитьПулСоединений(), "Пул один на источник") + Ожидаем.Что(Первый.ИсточникДанных().ПулСоединений() = Второй.ИсточникДанных().ПулСоединений(), "Пул один на источник") .ЭтоИстина(); Источник.Закрыть(); КонецПроцедуры +&Тест +Процедура МенеджерВозвращаетСвойИсточник() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник менеджера"); + Менеджер = Новый МенеджерСущностей(Источник); + + Ожидаем.Что(Менеджер.ИсточникДанных() = Источник, "Менеджер отдает источник, из которого создан").ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + // Транзакции нужен коннектор СУБД: КоннекторInMemory их не поддерживает. &Тест Процедура ТранзакцияПотокаОбщаяДляМенеджеровИсточника() Экспорт diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 5db8be5..1955577 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -179,7 +179,7 @@ Менеджер.НачатьТранзакцию(); Ожидаем - .Что(Менеджер.ПолучитьПулСоединений().СоединениеТекущегоПотокаИсполнения() <> Неопределено, + .Что(Менеджер.ИсточникДанных().ПулСоединений().СоединениеТекущегоПотокаИсполнения() <> Неопределено, "Поток получил соединение и при нулевом размере") .ЭтоИстина(); @@ -690,20 +690,22 @@ &Тест Процедура ОшибкаЗавершенияТранзакцииНеОставляетСоединениеЗаПотокомИсполнения() Экспорт - ПулПадающий = Новый ПулСоединенийСБД( - 1, + ИсточникПадающий = Новый ИсточникДанных( + "Падающий", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Истина) + ПараметрыТестовогоКоннектора(0, Ложь, Истина), + 1 ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); Попытка - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулПадающий); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ИсточникПадающий); Ожидаем .Что(РаботаСКоннекторами, "Фиксация завершилась ошибкой коннектора") - .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ПулПадающий)) + .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ИсточникПадающий)) .ВыбрасываетИсключение(); Ожидаем @@ -717,11 +719,11 @@ .Равно(0); Исключение - ПулПадающий.Закрыть(); + ИсточникПадающий.Закрыть(); ВызватьИсключение; КонецПопытки; - ПулПадающий.Закрыть(); + ИсточникПадающий.Закрыть(); КонецПроцедуры @@ -887,15 +889,15 @@ // Параметры вызова ЗафиксироватьТранзакциюПотокаИсполнения. // // Параметры: -// Пул - ПулСоединений - Пул, которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // // Возвращаемое значение: // Массив - Параметры метода. // -Функция ПараметрыЗавершенияТранзакции(Пул) +Функция ПараметрыЗавершенияТранзакции(Источник) Параметры = Новый Массив; - Параметры.Добавить(Пул); + Параметры.Добавить(Источник); Возврат Параметры; diff --git "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 0e607b2..1de6d55 100644 --- "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -1,8 +1,8 @@ // Хранилище сущностей: чтение, запись и транзакции. // -// Транзакция принадлежит потоку исполнения, а не хранилищу: она открывается на соединении из -// пула менеджера и распространяется на все операции потока. Хранилище, созданное в обход -// менеджера, работает собственным коннектором. +// Транзакция принадлежит потоку исполнения, а не хранилищу: она открывается на соединении +// источника данных и распространяется на все операции потока через любое хранилище и менеджер +// этого источника. // // BSLLS:MagicNumber-off // BSLLS:LatinAndCyrillicSymbolInWord-off From faf4e33eda83121c48d1a7393a5bd1c75c8de673 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 21:08:00 +0000 Subject: [PATCH 04/55] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6?= =?UTF-8?q?=D0=B5=D1=80=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA,=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9=20=D1=81=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=20=D1=81=D0=B0=D0=BC;=20=D0=B7=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20SonarQube?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Менеджер, созданный по устаревшим параметрам соединения, владеет неявным источником и закрывает его в Закрыть(): старый код рассчитывает, что закрытие менеджера закрывает соединения. Источник, переданный в конструктор, менеджер не трогает. Контракт описан в комментарии метода, документации менеджера и разделе о закрытии. По замечаниям SonarQube: пробелы после запятых в вызовах менеджера, слитые ветки условия в ПолучитьОдно, Возврат в заглушке тестового коннектора, подавление проверки адреса в тестах с localhost, имена без "Автозакрытие" в наборе пула источника. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\276\321\201\321\202\320\265\320\271.md" | 8 +++-- ...21\202\320\260\320\241\320\221\320\224.md" | 7 +++-- ...20\276\321\200\320\260\320\274\320\270.os" | 4 +-- ...20\276\321\201\321\202\320\265\320\271.os" | 30 +++++++++++++------ ...21\200\321\213\321\202\320\270\320\265.os" | 1 + ...21\207\320\275\320\270\320\272\320\260.os" | 1 + ...20\260\320\275\320\275\321\213\321\205.os" | 30 ++++++++++++++----- 7 files changed, 57 insertions(+), 24 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index c3bce77..c291da4 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -140,10 +140,12 @@ ### Закрыть ```bsl -// Освобождает модель данных менеджера. +// Освобождает модель данных менеджера и закрывает источник данных, если его создал менеджер. // -// Пул соединений и хранилища сущностей принадлежат источнику данных и здесь не закрываются: -// их закрывает ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. +// Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей +// принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер +// создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его +// соединения закрыты, а менеджер к БД больше не обращается. // Процедура Закрыть() ``` diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 4200e41..2b39f2f 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -140,9 +140,12 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" ### Закрытие -`МенеджерСущностей.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. +`ИсточникДанных.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. Тот же +эффект дает завершение потока, в котором источник создан, если автозакрытие не выключено. +`МенеджерСущностей.Закрыть()` закрывает источник только тогда, когда сам его создал: при создании +менеджера по устаревшим параметрам соединения. Закрытие терминально: после него пул соединений не выдает, и обращение к БД завершается ошибкой -`Пул соединений закрыт`. Иначе ожидающий, проснувшийся уже после закрытия, молча открыл бы себе +`Источник данных <Имя> закрыт`. Иначе ожидающий, проснувшийся уже после закрытия, молча открыл бы себе новое соединение, которое пережило бы закрытие пула, - и обещание «закрывает все соединения» перестало бы выполняться. diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 3ad0c34..a854d43 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -102,9 +102,7 @@ // Функция ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") Тогда - ПередаваемыеОпцииПоиска = ОпцииПоиска; - ИначеЕсли ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда + Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") ИЛИ ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда ПередаваемыеОпцииПоиска = ОпцииПоиска; Иначе ПередаваемыеОпцииПоиска = Новый ОпцииПоиска(); diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index fbd2e8c..efe7c39 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -11,6 +11,10 @@ // ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; +// Булево - Источник создан самим менеджером по устаревшим параметрам соединения, и закрывать +// его должен менеджер. +Перем ВладеетИсточником; + // Лог - Журнал библиотеки. Перем Лог; @@ -42,6 +46,7 @@ Если ТипЗнч(ИсточникДанных) = Тип("ИсточникДанных") Тогда Источник = ИсточникДанных; + ВладеетИсточником = Ложь; ПредупредитьОЛишнихПараметрах( УстарелСтрокаСоединения, УстарелПараметрыКоннектора, @@ -58,6 +63,7 @@ УстарелПараметрыКоннектора, УстарелРазмерПулаСоединений ); + ВладеетИсточником = Истина; КонецЕсли; Лог.Отладка("Инициализация менеджера сущностей с коннектором %1", Источник.ТипКоннектора()); @@ -188,7 +194,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Сохранить(Источник,ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -215,7 +221,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.Получить(Источник,ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -243,7 +249,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдно(Источник,ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -260,17 +266,23 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Удалить(Источник,ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры -// Освобождает модель данных менеджера. +// Освобождает модель данных менеджера и закрывает источник данных, если его создал менеджер. // -// Пул соединений и хранилища сущностей принадлежат источнику данных и здесь не закрываются: -// их закрывает ИсточникДанных.Закрыть() или завершение потока, в котором источник создан. +// Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей +// принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер +// создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его +// соединения закрыты, а менеджер к БД больше не обращается. // Процедура Закрыть() Экспорт + Если ВладеетИсточником Тогда + Источник.Закрыть(); + КонецЕсли; + МодельДанных.Очистить(); // Для освобожения ссылок на все коннекторы и соединения с СУБД @@ -317,7 +329,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннектором(Источник,Алгоритм, Контекст, Объект); + РаботаСКоннекторами.ВыполнитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -339,7 +351,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник,Алгоритм, Контекст, Объект); + Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецФункции // Источник данных, из которого создан менеджер. diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index da731b9..abea696 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -104,6 +104,7 @@ Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт ВызватьИсключение "Не поддерживается"; + Возврат Неопределено; КонецФункции Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 07ab30f..49b8bec 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -5,6 +5,7 @@ // // BSLLS:MagicNumber-off // BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off #Использовать ".." #Использовать "../src/internal" diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 6154586..4ce326f 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -4,6 +4,7 @@ // // BSLLS:MagicNumber-off // BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off #Использовать ".." #Использовать "utils" @@ -75,6 +76,21 @@ КонецПроцедуры +// Менеджер, созданный по устаревшим параметрам соединения, сам создает источник, поэтому сам его +// и закрывает: старый код рассчитывает, что Закрыть() закрывает соединения. +&Тест +Процедура ЗакрытиеМенеджераЗакрываетСозданныйИмИсточник() Экспорт + + Менеджер = Новый МенеджерСущностей(Тип("КоннекторInMemory"), "неявный источник"); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Менеджер.Закрыть(); + + Ожидаем.Что(Менеджер.ИсточникДанных().Закрыт(), "Источник, созданный менеджером, закрыт вместе с ним") + .ЭтоИстина(); + +КонецПроцедуры + &Тест Процедура ПослеЗакрытияИсточникаМенеджерНеРаботает() Экспорт @@ -108,13 +124,13 @@ КонецПроцедуры &Тест -Процедура АвтозакрытиеОтключаетсяСеттером() Экспорт +Процедура АвтоЗакрытиеОтключаетсяСеттером() Экспорт - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтозакрытияВДругомПотоке", Новый Массив, Истина); + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтоЗакрытияВДругомПотоке", Новый Массив, Истина); Задание.ОжидатьЗавершения(); Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); - Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Без автозакрытия источник переживает поток").ЭтоЛожь(); + Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Без автоматического закрытия источник переживает поток").ЭтоЛожь(); Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); Ожидаем.Что(Менеджер.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); @@ -218,12 +234,12 @@ КонецПроцедуры -// Создает источник без автозакрытия и открывает его пул в потоке фонового задания. Используется -// тестом "АвтозакрытиеОтключаетсяСеттером". +// Создает источник без автоматического закрытия и открывает его пул в потоке фонового задания. Используется +// тестом "АвтоЗакрытиеОтключаетсяСеттером". // -Процедура СоздатьИсточникБезАвтозакрытияВДругомПотоке() Экспорт +Процедура СоздатьИсточникБезАвтоЗакрытияВДругомПотоке() Экспорт - ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "без автозакрытия"); + ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "без автоматического закрытия"); ИсточникДругогоПотока.УстановитьАвтоЗакрытие(Ложь); Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); From 5a69ff6afcc5504c63f6dd2037b6d4791d2844ab Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 21:13:32 +0000 Subject: [PATCH 05/55] =?UTF-8?q?=D0=98=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=B1=D0=B5=D0=B7=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D1=83=D0=BB=D0=B0;=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Хранилище может появиться раньше пула: получение хранилища к БД не обращается. Закрыть() источника убирает его хранилища из кэша независимо от того, создан ли пул, иначе кэш держал закрытые источники навсегда. README и документация хранилища больше не говорят о собственных коннекторах менеджера и хранилища. Тесты источника закрывают созданные источники явно. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 2 +- ...20\276\321\201\321\202\320\265\320\271.md" | 3 +- ...20\260\320\275\320\275\321\213\321\205.os" | 9 +++-- ...21\207\320\275\320\270\320\272\320\260.os" | 39 ++++++++++++++++--- ...20\260\320\275\320\275\321\213\321\205.os" | 2 + 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ac31af0..444d045 100644 --- a/README.md +++ b/README.md @@ -528,7 +528,7 @@ > Важно! -Каждое ХранилищеСущностей и МенеджерСущностей хранят в себе отдельные экземпляры Коннекторов. Тип, строка соединения и параметры коннектора определяются при создании МенеджераСущностей. +Менеджеры и хранилища сущностей одного источника данных собственных коннекторов не держат: они работают общим пулом соединений этого источника. Тип, строка соединения и параметры коннектора задаются при создании ИсточникаДанных. diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index ddc06ce..9865698 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -92,7 +92,8 @@ ```bsl // Выполняет очистку текущего Хранилища сущностей. -// Дополнительно посылает коннектору запрос на закрытие соединения. +// +// Коннекторов хранилище не держит: они живут в пуле источника данных, и закрывает их источник. // Процедура Закрыть() Экспорт ``` diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 166f9c2..8695146 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -189,10 +189,13 @@ БлокировкаПула.Запись().Заблокировать(); Попытка - Если пПул <> Неопределено И НЕ пЗакрыт Тогда - // Хранилища сущностей работают источником: закрываются вместе с ним. + Если НЕ пЗакрыт Тогда + // Хранилища сущностей работают источником и могут появиться раньше пула: + // закрываются вместе с источником независимо от того, создан ли пул. ХранилищаСущностей.Закрыть(ЭтотОбъект); - пПул.Закрыть(); + Если пПул <> Неопределено Тогда + пПул.Закрыть(); + КонецЕсли; КонецЕсли; пЗакрыт = Истина; Исключение diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 49b8bec..cc54958 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -44,6 +44,27 @@ КонецПроцедуры +// Хранилище может появиться раньше пула: получение хранилища к БД не обращается. Закрытие +// источника должно убирать его хранилища из кэша и в этом случае, иначе кэш держит закрытые +// источники навсегда. +&Тест +Процедура КэшХранилищЗакрываетсяИБезСозданногоПула() Экспорт + + ПодключитьФикстурыАвторов(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без пула"); + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + Источник.Закрыть(); + + Ожидаем.Что(ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Хранилище убрано из кэша") + .ЭтоЛожь(); + +КонецПроцедуры + &Тест Процедура АлгоритмВыполняетсяКоннекторомЧерезИсточник() Экспорт @@ -105,11 +126,7 @@ // Функция МенеджерАвторов(Источник) - ПодключитьСценарий( - ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), - "СущностьБезГенерируемогоИдентификатора" - ); - ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + ПодключитьФикстурыАвторов(); Менеджер = Новый МенеджерСущностей(Источник); // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. @@ -121,6 +138,18 @@ КонецФункции +// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. +// +Процедура ПодключитьФикстурыАвторов() + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + +КонецПроцедуры + // Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL // по переменным среды. // diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index ecd14bc..b86950f 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -79,6 +79,7 @@ Ожидаем.Что(Пул.РазмерПула()).Равно(3); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры @@ -100,6 +101,7 @@ Ожидаем.Что(Пул.РазмерПула(), "Размер пула взят из источника").Равно(3); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры From 11ef45cd9f52782c8d8b58f4dc47679d48031b24 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 21:15:10 +0000 Subject: [PATCH 06/55] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D0=BB=D1=83=D1=88?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=87=D0=B0=D0=B5=D1=82=20=D0=BF=D1=83=D1=81=D1=82?= =?UTF-8?q?=D1=8B=D0=BC=20=D1=80=D0=B5=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SonarQube считал возврат после исключения недостижимым кодом. Данных у коннектора нет, поэтому поиск возвращает пустой массив, а не падает. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\202\320\272\321\200\321\213\321\202\320\270\320\265.os" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index abea696..2cf5261 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -86,7 +86,8 @@ // Операции над данными коннектор не поддерживает: он изображает только соединение. Методы // объявлены, чтобы коннектор отвечал интерфейсу АбстрактныйКоннектор, который проверяет -// ИсточникДанных. +// ИсточникДанных. Поиск отвечает пустым результатом: функции нужен возврат, а данных +// у коннектора нет. // // BSLLS:UnusedParameters-off @@ -103,8 +104,7 @@ КонецПроцедуры Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт - ВызватьИсключение "Не поддерживается"; - Возврат Неопределено; + Возврат Новый Массив; КонецФункции Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт From fdec01f69bcc7cb86e5f1d6fc1c0c19f8f1e6cd6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 21:17:56 +0000 Subject: [PATCH 07/55] =?UTF-8?q?=D0=9F=D0=BE=D0=BC=D0=BE=D1=89=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=20=D0=B1=D0=B5=D0=B7=20?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2=D0=B0,=20=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=20SonarQube=20=D1=81=D1=87=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\202\320\276\321\207\320\275\320\270\320\272\320\260.os" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index cc54958..e179adb 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -50,7 +50,7 @@ &Тест Процедура КэшХранилищЗакрываетсяИБезСозданногоПула() Экспорт - ПодключитьФикстурыАвторов(); + ПодключитьКлассыСущностей(); Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без пула"); Менеджер = Новый МенеджерСущностей(Источник); @@ -126,7 +126,7 @@ // Функция МенеджерАвторов(Источник) - ПодключитьФикстурыАвторов(); + ПодключитьКлассыСущностей(); Менеджер = Новый МенеджерСущностей(Источник); // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. @@ -140,7 +140,7 @@ // Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. // -Процедура ПодключитьФикстурыАвторов() +Процедура ПодключитьКлассыСущностей() ПодключитьСценарий( ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), From e21ec61b394bb00a991f139f5fe21413c7417e80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 07:25:49 +0000 Subject: [PATCH 08/55] =?UTF-8?q?=D0=9F=D1=83=D0=BB=20=D1=81=D0=BE=D0=B5?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9=20=D1=81=D0=BE=D0=B7?= =?UTF-8?q?=D0=B4=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B5=20=D1=81=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=BC,=20=D0=B0=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE=D0=BC=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Конструктор пула соединений не открывает: он проверяет размер и заводит очереди, поэтому ленивое создание ничего не экономило, а стоило двойной проверки под блокировкой и состояния "источник живой, пула нет". Теперь ПулСоединений() только проверяет, что источник не закрыт, а Закрыть() всегда закрывает пул. Неверный размер пула отвергается при создании источника. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.md" | 13 +++--- ...20\260\320\275\320\275\321\213\321\205.os" | 46 ++++++------------- ...21\207\320\275\320\270\320\272\320\260.os" | 8 ++-- ...20\260\320\275\320\275\321\213\321\205.os" | 16 +++++++ ...20\260\320\275\320\275\321\213\321\205.os" | 4 +- 5 files changed, 43 insertions(+), 44 deletions(-) diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index 786a299..a23ae85 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -3,11 +3,12 @@ Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула и сам пул соединений одним объектом. -Пул создается при первом обращении и общий для всех, кто работает через источник. Закрывает -пул сам источник: методом `Закрыть()` или по завершению потока, в котором источник создан, если -автозакрытие не выключено, см. `УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый -источник соединений не выдает. Тип коннектора проверяется при создании: тип без интерфейса -`АбстрактныйКоннектор` отвергается с ошибкой. +Пул создается вместе с источником и общий для всех, кто работает через источник; соединения +пул открывает при первом обращении. Закрывает пул сам источник: методом `Закрыть()` или по +завершению потока, в котором источник создан, если автозакрытие не выключено, см. +`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник соединений не выдает. +Тип коннектора и размер пула проверяются при создании: тип без интерфейса +`АбстрактныйКоннектор` и отрицательный размер отвергаются с ошибкой. ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); @@ -140,7 +141,7 @@ ```bsl // @internal -// Пул соединений источника; создается при первом обращении. +// Пул соединений источника. // // Возвращаемое значение: // ПулСоединенийСБД - Пул соединений. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 8695146..fae2f40 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -14,9 +14,9 @@ Перем пПараметрыКоннектора; // Число - Предел одновременно живущих соединений с БД; 0 - без предела. Перем пРазмерПула; -// ПулСоединенийСБД, Неопределено - Пул соединений; создается при первом обращении. +// ПулСоединенийСБД - Пул соединений источника. Перем пПул; -// БлокировкаЧтенияЗаписи - Защищает создание и закрытие пула. +// БлокировкаЧтенияЗаписи - Сериализует закрытие: оно должно выполниться один раз. Перем БлокировкаПула; // Булево - Закрывать источник по завершению потока, в котором он создан. Перем пАвтоЗакрытие; @@ -40,11 +40,12 @@ // Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула // и сам пул соединений одним объектом. // -// Пул создается при первом обращении и общий для всех, кто работает через источник. Закрывает -// пул сам источник: методом Закрыть() или по завершению потока, в котором источник создан, если -// автозакрытие не выключено, см. УстановитьАвтоЗакрытие. Закрытие терминально: закрытый -// источник соединений не выдает. Тип коннектора проверяется при создании: тип без интерфейса -// АбстрактныйКоннектор отвергается с ошибкой. +// Пул создается вместе с источником и общий для всех, кто работает через источник; соединения +// пул открывает при первом обращении. Закрывает пул сам источник: методом Закрыть() или по +// завершению потока, в котором источник создан, если автозакрытие не выключено, см. +// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник соединений не выдает. +// Тип коннектора и размер пула проверяются при создании: тип без интерфейса +// АбстрактныйКоннектор и отрицательный размер отвергаются с ошибкой. // // Параметры: // Имя - Строка - Имя источника данных. @@ -82,7 +83,7 @@ пПараметрыКоннектора = Новый ФиксированныйМассив(ПараметрыКоннектора); КонецЕсли; - пПул = Неопределено; + пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); БлокировкаПула = Новый БлокировкаЧтенияЗаписи; пАвтоЗакрытие = Истина; пЗакрыт = Ложь; @@ -190,12 +191,9 @@ Попытка Если НЕ пЗакрыт Тогда - // Хранилища сущностей работают источником и могут появиться раньше пула: - // закрываются вместе с источником независимо от того, создан ли пул. + // Хранилища сущностей работают источником: закрываются вместе с ним. ХранилищаСущностей.Закрыть(ЭтотОбъект); - Если пПул <> Неопределено Тогда - пПул.Закрыть(); - КонецЕсли; + пПул.Закрыть(); КонецЕсли; пЗакрыт = Истина; Исключение @@ -219,33 +217,17 @@ // BSLLS:UsingServiceTag-off // @internal // BSLLS:UsingServiceTag-on -// Пул соединений источника; создается при первом обращении. +// Пул соединений источника. // // Возвращаемое значение: // ПулСоединенийСБД - Пул соединений. // Функция ПулСоединений() Экспорт - Если пПул <> Неопределено И НЕ пЗакрыт Тогда - Возврат пПул; + Если пЗакрыт Тогда + ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", пИмя); КонецЕсли; - БлокировкаПула.Запись().Заблокировать(); - - Попытка - Если пЗакрыт Тогда - ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", пИмя); - КонецЕсли; - Если пПул = Неопределено Тогда - пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); - КонецЕсли; - Исключение - БлокировкаПула.Запись().Разблокировать(); - ВызватьИсключение; - КонецПопытки; - - БлокировкаПула.Запись().Разблокировать(); - Возврат пПул; КонецФункции diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index e179adb..59d7233 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -44,15 +44,15 @@ КонецПроцедуры -// Хранилище может появиться раньше пула: получение хранилища к БД не обращается. Закрытие -// источника должно убирать его хранилища из кэша и в этом случае, иначе кэш держит закрытые +// Получение хранилища к БД не обращается. Закрытие источника должно убирать его хранилища из +// кэша и тогда, когда ни одного соединения открыто не было, иначе кэш держит закрытые // источники навсегда. &Тест -Процедура КэшХранилищЗакрываетсяИБезСозданногоПула() Экспорт +Процедура КэшХранилищЗакрываетсяИБезОткрытыхСоединений() Экспорт ПодключитьКлассыСущностей(); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без пула"); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без соединений"); Менеджер = Новый МенеджерСущностей(Источник); Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); Менеджер.ДобавитьКлассВМодель(Тип("Автор")); diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index b86950f..93ab2fe 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -34,6 +34,22 @@ КонецПроцедуры +// Пул создается вместе с источником, поэтому и размер пула проверяется при создании. +&Тест +Процедура НеверныйРазмерПулаОтвергаетсяПриСоздании() Экспорт + + ТекстОшибки = ""; + Попытка + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "", , -1); // BSLLS:UnusedLocalVariable-off + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Источник отвергает отрицательный размер пула") + .Содержит("Размер пула соединений должен быть неотрицательным числом"); + +КонецПроцедуры + &Тест Процедура РазмерПулаЗадаетсяПятымПараметром() Экспорт diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 4ce326f..70ee610 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -223,7 +223,7 @@ КонецФункции -// Создает источник и открывает его пул в потоке фонового задания. Используется тестом +// Создает источник и открывает соединение в потоке фонового задания. Используется тестом // "ИсточникЗакрываетсяПоЗавершениюПотока". // Процедура СоздатьИсточникВДругомПотоке() Экспорт @@ -234,7 +234,7 @@ КонецПроцедуры -// Создает источник без автоматического закрытия и открывает его пул в потоке фонового задания. Используется +// Создает источник без автоматического закрытия и открывает соединение в потоке фонового задания. Используется // тестом "АвтоЗакрытиеОтключаетсяСеттером". // Процедура СоздатьИсточникБезАвтоЗакрытияВДругомПотоке() Экспорт From f7b3e13c31e5b03d36f7d7727bbde29fab2afbf3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 07:33:47 +0000 Subject: [PATCH 09/55] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B2=D1=8B=D0=B4=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Получение хранилища к БД не обращается, поэтому после Закрыть() менеджер создавал хранилище заново, и оно ложилось в кэш навсегда. Кэш хранилищ проверяет признак закрытия источника под своей блокировкой и отказывает, а источник ставит признак до уборки хранилищ: после уборки новое хранилище закрытому источнику уже не создается. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.md" | 4 ++-- ...20\276\321\201\321\202\320\265\320\271.os" | 7 ++++++- ...20\260\320\275\320\275\321\213\321\205.os" | 8 ++++--- ...21\207\320\275\320\270\320\272\320\260.os" | 21 ++++++++++++------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index a23ae85..c0f2256 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -6,8 +6,8 @@ Пул создается вместе с источником и общий для всех, кто работает через источник; соединения пул открывает при первом обращении. Закрывает пул сам источник: методом `Закрыть()` или по завершению потока, в котором источник создан, если автозакрытие не выключено, см. -`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник соединений не выдает. -Тип коннектора и размер пула проверяются при создании: тип без интерфейса +`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник не выдает ни соединений, +ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса `АбстрактныйКоннектор` и отрицательный размер отвергаются с ошибкой. ```bsl diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 8e00414..cd5da93 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -12,7 +12,8 @@ // БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. Перем Блокировка; -// Возвращает хранилище сущностей, создавая его при первом обращении. +// Возвращает хранилище сущностей, создавая его при первом обращении. Для закрытого источника +// хранилище не создается: обращение завершается ошибкой. // // Параметры: // ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. @@ -40,6 +41,10 @@ // Пока ждали запись, хранилище мог создать другой поток. Хранилище = НайтиХранилище(Источник, ТипСущности); Если Хранилище = Неопределено Тогда + // Закрытый источник свои хранилища уже убрал: новое легло бы в кэш навсегда. + Если Источник.Закрыт() Тогда + ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", Источник.Имя()); + КонецЕсли; ХранилищаИсточника = Хранилища.Получить(Источник); Если ХранилищаИсточника = Неопределено Тогда ХранилищаИсточника = Новый Соответствие; diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index fae2f40..67448d8 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -43,8 +43,8 @@ // Пул создается вместе с источником и общий для всех, кто работает через источник; соединения // пул открывает при первом обращении. Закрывает пул сам источник: методом Закрыть() или по // завершению потока, в котором источник создан, если автозакрытие не выключено, см. -// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник соединений не выдает. -// Тип коннектора и размер пула проверяются при создании: тип без интерфейса +// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник не выдает ни соединений, +// ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса // АбстрактныйКоннектор и отрицательный размер отвергаются с ошибкой. // // Параметры: @@ -191,11 +191,13 @@ Попытка Если НЕ пЗакрыт Тогда + // Признак ставится до уборки хранилищ: кэш хранилищ проверяет его под своей блокировкой + // и после уборки нового хранилища закрытому источнику уже не создаст. + пЗакрыт = Истина; // Хранилища сущностей работают источником: закрываются вместе с ним. ХранилищаСущностей.Закрыть(ЭтотОбъект); пПул.Закрыть(); КонецЕсли; - пЗакрыт = Истина; Исключение БлокировкаПула.Запись().Разблокировать(); ВызватьИсключение; diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 59d7233..83b04b9 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -44,24 +44,29 @@ КонецПроцедуры -// Получение хранилища к БД не обращается. Закрытие источника должно убирать его хранилища из -// кэша и тогда, когда ни одного соединения открыто не было, иначе кэш держит закрытые -// источники навсегда. +// Получение хранилища к БД не обращается, поэтому закрытый источник должен отказывать в нем сам: +// иначе хранилище закрытого источника легло бы в кэш, откуда его уже никто не уберет. &Тест -Процедура КэшХранилищЗакрываетсяИБезОткрытыхСоединений() Экспорт +Процедура ЗакрытыйИсточникХранилищНеВыдает() Экспорт ПодключитьКлассыСущностей(); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без соединений"); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище после закрытия"); Менеджер = Новый МенеджерСущностей(Источник); Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); Менеджер.ДобавитьКлассВМодель(Тип("Автор")); - ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); Источник.Закрыть(); - Ожидаем.Что(ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Хранилище убрано из кэша") - .ЭтоЛожь(); + ТекстОшибки = ""; + Попытка + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Хранилище закрытого источника не выдается и в кэш не попадает").Содержит("закрыт"); КонецПроцедуры From 8a3e70aec5966145071414078bd37708052c0583 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 08:44:20 +0000 Subject: [PATCH 10/55] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B7=D0=BD=D0=B0?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B8=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7=D0=B0=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D0=B8=D1=8F=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D1=81=D1=82=D0=B0=D0=BB=D0=B8=20=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D0=BC=D0=B0=D1=80=D0=BD=D1=8B=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Источник держал блокировку чтения-записи только в Закрыть(), а признак закрытия читал без нее: половина блокировки не использовалась, и чтение шло мимо защиты. Взять чтение нельзя: кэш хранилищ зовет Закрыт() под своей блокировкой записи, а Закрыть() брал блокировку кэша под блокировкой источника, что дает взаимную блокировку. Оба признака переведены на АтомарноеБулево из sfaqer/atomic. Однократность закрытия дает СравнитьИУстановить, поперек вызовов в кэш и пул источник блокировок не держит: они потокобезопасны сами. Блокировка чтения-записи остается там, где есть параллельные читатели и редкий писатель: в кэше хранилищ и пуле соединений. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- packagedef | 1 + ...20\260\320\275\320\275\321\213\321\205.os" | 51 ++++++++----------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/packagedef b/packagedef index 022b5c4..f4ad496 100644 --- a/packagedef +++ b/packagedef @@ -17,6 +17,7 @@ .ВключитьФайл("LICENSE.md") .ЗависитОт("annotations", "1.6.0") .ЗависитОт("asserts", "1.6.2") + .ЗависитОт("atomic", "0.1.1") .ЗависитОт("collectionos", "0.8.3") .ЗависитОт("fluent", "0.6.1") .ЗависитОт("fs", "1.2.0") diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 67448d8..1d8432b 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -1,7 +1,7 @@ #Использовать "../internal" -#Использовать "../internal/concurrent" #Использовать asserts +#Использовать atomic #Использовать reflector // Строка - Имя источника данных, по которому сущности выбирают источник в аннотации &Сущность. @@ -16,11 +16,11 @@ Перем пРазмерПула; // ПулСоединенийСБД - Пул соединений источника. Перем пПул; -// БлокировкаЧтенияЗаписи - Сериализует закрытие: оно должно выполниться один раз. -Перем БлокировкаПула; -// Булево - Закрывать источник по завершению потока, в котором он создан. +// АтомарноеБулево - Закрывать источник по завершению потока, в котором он создан. Атомарное: +// сеттер зовут из одного потока, обработчик завершения читает в другом. Перем пАвтоЗакрытие; -// Булево - Источник закрыт: пул закрыт и соединений больше не выдает. +// АтомарноеБулево - Источник закрыт: пул закрыт и соединений больше не выдает. Атомарное: +// закрывает один поток, читают все, кто работает через источник. Перем пЗакрыт; // Вызывается движком по завершению потока, создавшего источник: при включенном автозакрытии @@ -31,7 +31,7 @@ // // BSLLS:UnusedLocalMethod-off Процедура ПриЗавершенииПотока() - Если пАвтоЗакрытие Тогда + Если пАвтоЗакрытие.Получить() Тогда Закрыть(); КонецЕсли; КонецПроцедуры @@ -84,9 +84,8 @@ КонецЕсли; пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); - БлокировкаПула = Новый БлокировкаЧтенияЗаписи; - пАвтоЗакрытие = Истина; - пЗакрыт = Ложь; + пАвтоЗакрытие = Новый АтомарноеБулево(Истина); + пЗакрыт = Новый АтомарноеБулево(Ложь); // Источник живет не дольше создавшего его потока: о завершении сообщит сам поток. ДобавитьОбработчик ТекущийПоток().ПриЗавершении, ПриЗавершенииПотока; @@ -168,7 +167,7 @@ // Значение - Булево - Истина - закрывать по завершению потока; Ложь - только явным Закрыть(). // Процедура УстановитьАвтоЗакрытие(Знач Значение) Экспорт - пАвтоЗакрытие = Значение; + пАвтоЗакрытие.Установить(Значение); КонецПроцедуры // Признак закрытия источника по завершению потока, см. УстановитьАвтоЗакрытие. @@ -177,7 +176,7 @@ // Булево - Истина, если источник закрывается по завершению создавшего его потока. // Функция АвтоЗакрытие() Экспорт - Возврат пАвтоЗакрытие; + Возврат пАвтоЗакрытие.Получить(); КонецФункции // Закрывает пул соединений источника: все его соединения закрываются. @@ -187,23 +186,17 @@ // Процедура Закрыть() Экспорт - БлокировкаПула.Запись().Заблокировать(); - - Попытка - Если НЕ пЗакрыт Тогда - // Признак ставится до уборки хранилищ: кэш хранилищ проверяет его под своей блокировкой - // и после уборки нового хранилища закрытому источнику уже не создаст. - пЗакрыт = Истина; - // Хранилища сущностей работают источником: закрываются вместе с ним. - ХранилищаСущностей.Закрыть(ЭтотОбъект); - пПул.Закрыть(); - КонецЕсли; - Исключение - БлокировкаПула.Запись().Разблокировать(); - ВызватьИсключение; - КонецПопытки; + // Признак переключается атомарно и до уборки хранилищ: закрытие выполняется один раз, а кэш + // хранилищ, проверяющий признак под своей блокировкой, после уборки нового хранилища + // закрытому источнику уже не создаст. Своей блокировки поперек вызовов в кэш и пул источник + // не держит: они потокобезопасны сами, а вложенное ожидание вело бы к взаимной блокировке. + Если НЕ пЗакрыт.СравнитьИУстановить(Ложь, Истина) Тогда + Возврат; + КонецЕсли; - БлокировкаПула.Запись().Разблокировать(); + // Хранилища сущностей работают источником: закрываются вместе с ним. + ХранилищаСущностей.Закрыть(ЭтотОбъект); + пПул.Закрыть(); КонецПроцедуры @@ -213,7 +206,7 @@ // Булево - Истина после закрытия, явного или по завершению потока. // Функция Закрыт() Экспорт - Возврат пЗакрыт; + Возврат пЗакрыт.Получить(); КонецФункции // BSLLS:UsingServiceTag-off @@ -226,7 +219,7 @@ // Функция ПулСоединений() Экспорт - Если пЗакрыт Тогда + Если пЗакрыт.Получить() Тогда ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", пИмя); КонецЕсли; From 7b94b630500bb08121cf7a30646a78dff3f5b11b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 08:51:51 +0000 Subject: [PATCH 11/55] =?UTF-8?q?=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D0=B4=D0=B5=D0=BB=D0=B0=D0=B5=D1=82=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Если закрытие пула упало на одном из соединений, источник оставался помеченным закрытым, и повторный Закрыть() ничего не делал: остальные соединения оставались без хозяина. Теперь при ошибке признак сбрасывается, и закрытие можно повторить. Тестовый коннектор умеет падать при закрытии. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.md" | 3 +- ...20\260\320\275\320\275\321\213\321\205.os" | 14 +++++--- ...21\200\321\213\321\202\320\270\320\265.os" | 10 +++++- ...20\270\320\271\320\241\320\221\320\224.os" | 36 ++++++++++++++++++- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index c0f2256..86fb183 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -121,7 +121,8 @@ // Закрывает пул соединений источника: все его соединения закрываются. // // Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный -// вызов ничего не делает. +// вызов ничего не делает. Если закрытие завершилось ошибкой, источник закрытым не считается, +// и закрытие можно повторить: иначе оставшиеся соединения некому было бы закрыть. // Процедура Закрыть() Экспорт ``` diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 1d8432b..c3dfa74 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -182,7 +182,8 @@ // Закрывает пул соединений источника: все его соединения закрываются. // // Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный -// вызов ничего не делает. +// вызов ничего не делает. Если закрытие завершилось ошибкой, источник закрытым не считается, +// и закрытие можно повторить: иначе оставшиеся соединения некому было бы закрыть. // Процедура Закрыть() Экспорт @@ -194,9 +195,14 @@ Возврат; КонецЕсли; - // Хранилища сущностей работают источником: закрываются вместе с ним. - ХранилищаСущностей.Закрыть(ЭтотОбъект); - пПул.Закрыть(); + Попытка + // Хранилища сущностей работают источником: закрываются вместе с ним. + ХранилищаСущностей.Закрыть(ЭтотОбъект); + пПул.Закрыть(); + Исключение + пЗакрыт.Установить(Ложь); + ВызватьИсключение; + КонецПопытки; КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index 2cf5261..9f7de3c 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -8,6 +8,7 @@ // рукопожатие, аутентификацию, недоступный хост. // [1] - Булево - Истина, если открытие должно завершиться ошибкой. // [2] - Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. +// [3] - Булево - Истина, если закрытие должно завершиться ошибкой. // // BSLLS:MagicNumber-off @@ -15,10 +16,13 @@ Перем Открыт; // Булево - Признак того, что завершение транзакции должно падать. Перем ПадатьПриЗавершенииТранзакции; +// Булево - Признак того, что закрытие должно падать. +Перем ПадатьПриЗакрытии; Процедура ПриСозданииОбъекта() Открыт = Ложь; ПадатьПриЗавершенииТранзакции = Ложь; + ПадатьПриЗакрытии = Ложь; КонецПроцедуры // Открывает соединение так, как задано параметрами коннектора. @@ -38,6 +42,7 @@ КонецЕсли; ПадатьПриЗавершенииТранзакции = ПараметрыКоннектора.Количество() > 2 И ПараметрыКоннектора[2]; + ПадатьПриЗакрытии = ПараметрыКоннектора.Количество() > 3 И ПараметрыКоннектора[3]; Открыт = Истина; @@ -69,9 +74,12 @@ КонецЕсли; КонецПроцедуры -// Закрывает соединение. +// Закрывает соединение, падая, если так задано параметрами. // Процедура Закрыть() Экспорт + Если ПадатьПриЗакрытии Тогда + ВызватьИсключение "Закрытие соединения не удалось"; + КонецЕсли; Открыт = Ложь; КонецПроцедуры diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 1955577..bfeb2d8 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -727,6 +727,37 @@ КонецПроцедуры +// Ошибка при закрытии соединения не должна делать источник закрытым: иначе повторный вызов +// ничего не сделает, и оставшиеся соединения некому закрыть. +// +&Тест +Процедура ОшибкаЗакрытияНеДелаетИсточникЗакрытым() Экспорт + + ИсточникПадающий = Новый ИсточникДанных( + "Падающий при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Истина), + 1 + ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); + ПулПадающий.Занять(); + ПулПадающий.Освободить(); + + Ожидаем + .Что(ИсточникПадающий, "Закрытие завершилось ошибкой коннектора") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем.Что(ИсточникПадающий.Закрыт(), "Источник не считается закрытым после ошибки").ЭтоЛожь(); + + // Соединение, на котором споткнулось закрытие, из очереди уже выбыло: повтор закрывает остаток. + ИсточникПадающий.Закрыть(); + + Ожидаем.Что(ИсточникПадающий.Закрыт(), "Повторное закрытие доводит дело до конца").ЭтоИстина(); + +КонецПроцедуры + // Закрытие пула терминально. Иначе ожидающий, проснувшийся после закрытия, молча открыл бы // себе новое соединение, которое пережило бы закрытие пула. // @@ -867,6 +898,7 @@ // ПаузаОткрытия - Число - Пауза внутри открытия в миллисекундах. // ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. // ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. +// ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. // // Возвращаемое значение: // Массив - Параметры коннектора. @@ -874,13 +906,15 @@ Функция ПараметрыТестовогоКоннектора( Знач ПаузаОткрытия, Знач ПадатьПриОткрытии, - Знач ПадатьПриЗавершенииТранзакции = Ложь + Знач ПадатьПриЗавершенииТранзакции = Ложь, + Знач ПадатьПриЗакрытии = Ложь ) Параметры = Новый Массив; Параметры.Добавить(ПаузаОткрытия); Параметры.Добавить(ПадатьПриОткрытии); Параметры.Добавить(ПадатьПриЗавершенииТранзакции); + Параметры.Добавить(ПадатьПриЗакрытии); Возврат Параметры; From 3fb1411ef0c8e8cec6910b3f308a33452a8fdff0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 09:25:37 +0000 Subject: [PATCH 12/55] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=B0=20=D0=BA=20=D0=BF=D1=83=D0=BB?= =?UTF-8?q?=D1=83=20=D0=B2=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=B5;=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B0=20=D0=BC?= =?UTF-8?q?=D1=83=D1=81=D0=BE=D1=80=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D1=81=D0=B5=D0=BD=D0=B0=20=D0=B2=20=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20=D0=B8=D1=81=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Размер пула в документации задается через ИсточникДанных, а не через устаревшие параметры менеджера; описание владения хранилищами и закрытия согласовано с кодом на обоих уровнях. ВыполнитьСборкуМусора() вызывается в ИсточникДанных.Закрыть() после закрытия пула, а не в менеджере. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 2 +- ...20\260\320\275\320\275\321\213\321\205.md" | 4 ++-- ...20\276\321\201\321\202\320\265\320\271.md" | 2 +- ...21\202\320\260\320\241\320\221\320\224.md" | 22 +++++++++++-------- ...20\260\320\275\320\275\321\213\321\205.os" | 8 +++++-- ...20\276\321\201\321\202\320\265\320\271.os" | 5 +---- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 444d045..1385bdd 100644 --- a/README.md +++ b/README.md @@ -491,7 +491,7 @@ ### МенеджерСущностей -МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который описывает тип *коннектора* к используемой базе данных, строку соединения и размер пула; создание менеджера напрямую по параметрам соединения устарело и сохранено для совместимости. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: +МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который задает тип *коннектора* к используемой базе данных, строку соединения и размер пула и владеет пулом соединений; создание менеджера напрямую по параметрам соединения устарело и сохранено для совместимости. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: * Создание и наполнение МоделиДанных * Трансляция запросов от прикладной логики к коннекторам diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index 86fb183..63f5fcb 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -8,7 +8,7 @@ завершению потока, в котором источник создан, если автозакрытие не выключено, см. `УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник не выдает ни соединений, ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса -`АбстрактныйКоннектор` и отрицательный размер отвергаются с ошибкой. +`АбстрактныйКоннектор` и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); @@ -142,7 +142,7 @@ ```bsl // @internal -// Пул соединений источника. +// Пул соединений источника. Для закрытого источника завершается ошибкой. // // Возвращаемое значение: // ПулСоединенийСБД - Пул соединений. diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index c291da4..8157d3a 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -145,7 +145,7 @@ // Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей // принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер // создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его -// соединения закрыты, а менеджер к БД больше не обращается. +// соединения закрыты. Модель данных очищается в любом случае, и к работе менеджер непригоден. // Процедура Закрыть() ``` diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 2b39f2f..64e7c1e 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -52,15 +52,17 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" ### Размер пула -Размер пула задается четвертым параметром конструктора и по умолчанию равен 10: +Размер пула задается пятым параметром конструктора `ИсточникДанных` и по умолчанию равен 10: ```bsl -МенеджерСущностей = Новый МенеджерСущностей( +Источник = Новый ИсточникДанных( + "Основной", Тип("КоннекторPostgreSQL"), "Host=localhost;Port=5432;Database=test", Неопределено, 10 ); +МенеджерСущностей = Новый МенеджерСущностей(Источник); ``` Размер - это предел параллельной работы с БД: он ограничивает число мест в пуле, а место занимает @@ -82,12 +84,12 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" за каждое обращение платится установкой соединения. ```bsl -МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), СтрокаСоединения, Неопределено, 0); +Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения, , 0); ``` Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника работают -одним пулом, поэтому предел общий для источника. Пул - обязательная зависимость хранилища: без -менеджера хранилище не создается. +одним пулом, поэтому предел общий для источника. Хранилище работает источником, из которого +создано, и своих соединений не держит. ### Незавершенные транзакции @@ -145,7 +147,9 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" `МенеджерСущностей.Закрыть()` закрывает источник только тогда, когда сам его создал: при создании менеджера по устаревшим параметрам соединения. -Закрытие терминально: после него пул соединений не выдает, и обращение к БД завершается ошибкой -`Источник данных <Имя> закрыт`. Иначе ожидающий, проснувшийся уже после закрытия, молча открыл бы себе -новое соединение, которое пережило бы закрытие пула, - и обещание «закрывает все соединения» -перестало бы выполняться. +Закрытие терминально на обоих уровнях. Закрытый источник соединений не выдает: обращение через +него завершается ошибкой `Источник данных <Имя> закрыт`. Закрытый пул тоже: ожидающий, +проснувшийся уже после закрытия, получает ошибку `Пул соединений закрыт`, а не открывает себе +молча новое соединение, которое пережило бы закрытие, - иначе обещание «закрывает все соединения» +перестало бы выполняться. Если закрытие завершилось ошибкой, источник закрытым не считается, и +закрытие можно повторить. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index c3dfa74..b153ea6 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -45,7 +45,7 @@ // завершению потока, в котором источник создан, если автозакрытие не выключено, см. // УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник не выдает ни соединений, // ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса -// АбстрактныйКоннектор и отрицательный размер отвергаются с ошибкой. +// АбстрактныйКоннектор и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. // // Параметры: // Имя - Строка - Имя источника данных. @@ -204,6 +204,10 @@ ВызватьИсключение; КонецПопытки; + // Закрытые соединения и коннекторы больше никому не нужны: освобождаем их сразу, не дожидаясь + // сборщика. + ВыполнитьСборкуМусора(); + КонецПроцедуры // Источник закрыт, см. Закрыть. @@ -218,7 +222,7 @@ // BSLLS:UsingServiceTag-off // @internal // BSLLS:UsingServiceTag-on -// Пул соединений источника. +// Пул соединений источника. Для закрытого источника завершается ошибкой. // // Возвращаемое значение: // ПулСоединенийСБД - Пул соединений. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index efe7c39..eda4ef1 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -275,7 +275,7 @@ // Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей // принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер // создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его -// соединения закрыты, а менеджер к БД больше не обращается. +// соединения закрыты. Модель данных очищается в любом случае, и к работе менеджер непригоден. // Процедура Закрыть() Экспорт @@ -285,9 +285,6 @@ МодельДанных.Очистить(); - // Для освобожения ссылок на все коннекторы и соединения с СУБД - ВыполнитьСборкуМусора(); - КонецПроцедуры // Начинает транзакцию для текущего потока исполнения. From 1c2989a9eff1fe5e215019619fd4e2dca443a131 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 09:42:14 +0000 Subject: [PATCH 13/55] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA,=20=D0=B0=20=D0=BD=D0=B5=20?= =?UTF-8?q?=C2=AB=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Творительный падеж утверждал, что хранилище само является источником данных. Формулировки в документации и комментариях приведены к «работает через источник». Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\261\320\276\321\202\320\260\320\241\320\221\320\224.md" | 4 ++-- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.md" | 2 +- ...20\272\320\224\320\260\320\275\320\275\321\213\321\205.os" | 2 +- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.os" | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 64e7c1e..8496a05 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -87,8 +87,8 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения, , 0); ``` -Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника работают -одним пулом, поэтому предел общий для источника. Хранилище работает источником, из которого +Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника делят +один пул, поэтому предел общий для источника. Хранилище работает через источник, из которого создано, и своих соединений не держит. ### Незавершенные транзакции diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 9865698..9e7f7d3 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -3,7 +3,7 @@ ### ПриСозданииОбъекта ```bsl -// Создает хранилище, привязанное к типу сущности и работающее переданным источником данных. +// Создает хранилище, привязанное к типу сущности и работающее через переданный источник данных. // // Создается пулом хранилищ сущностей, напрямую конструировать не следует. // diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index b153ea6..0b33f19 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -196,7 +196,7 @@ КонецЕсли; Попытка - // Хранилища сущностей работают источником: закрываются вместе с ним. + // Хранилища сущностей работают через источник: закрываются вместе с ним. ХранилищаСущностей.Закрыть(ЭтотОбъект); пПул.Закрыть(); Исключение diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index c2e0885..b2d56f5 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -12,7 +12,7 @@ // ИсточникДанных - Источник, соединением которого выполняется каждая операция. Перем Источник; -// Создает хранилище, привязанное к типу сущности и работающее переданным источником данных. +// Создает хранилище, привязанное к типу сущности и работающее через переданный источник данных. // // Создается пулом хранилищ сущностей, напрямую конструировать не следует. // From ea8184261d30cc8a30e7ce29b60eac4935bea656 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 15:11:45 +0000 Subject: [PATCH 14/55] =?UTF-8?q?entity=20v5:=20=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=B4=D0=B6=D0=B5=D1=80=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B5=D0=B9=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Устаревший путь создания МенеджерСущностей по параметрам соединения убран целиком: конструктор принимает один параметр ИсточникДанных и отвергает все остальное. Вместе с ним ушли неявный источник, признак владения им и предупреждения о лишних параметрах, а Закрыть() теперь только очищает модель данных. Версия по схеме репозитория поднята с 4.4.1.0 до 5.4.0.0: ломается API менеджера сущностей, API коннекторов не меняется. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 2 +- ...20\276\321\201\321\202\320\265\320\271.md" | 23 ++--- packagedef | 2 +- ...20\276\321\201\321\202\320\265\320\271.os" | 89 +++---------------- ...20\265\320\272\321\202\320\276\321\200.os" | 2 +- ...21\202\320\276\321\200\320\276\320\274.os" | 14 ++- ...20\260\320\275\320\275\321\213\321\205.os" | 50 +++-------- ...20\272\321\202\320\276\321\200InMemory.os" | 10 +-- ...65\320\272\321\202\320\276\321\200JSON.os" | 8 +- ...\272\321\202\320\276\321\200PostgreSQL.os" | 7 +- ...\320\272\321\202\320\276\321\200SQLite.os" | 7 +- ...20\260\320\275\320\275\321\213\321\205.os" | 15 ---- ...20\270\320\271\320\241\320\221\320\224.os" | 13 ++- ...20\276\321\201\321\202\320\265\320\271.os" | 7 +- 14 files changed, 80 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index 1385bdd..4690af8 100644 --- a/README.md +++ b/README.md @@ -491,7 +491,7 @@ ### МенеджерСущностей -МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который задает тип *коннектора* к используемой базе данных, строку соединения и размер пула и владеет пулом соединений; создание менеджера напрямую по параметрам соединения устарело и сохранено для совместимости. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: +МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который задает тип *коннектора* к используемой базе данных, строку соединения и размер пула и владеет пулом соединений; других способов создать менеджер нет. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: * Создание и наполнение МоделиДанных * Трансляция запросов от прикладной логики к коннекторам diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 8157d3a..7847275 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -6,26 +6,17 @@ // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного -// источника делят соединения и транзакции потоков. Создание по параметрам соединения устарело и -// отмечается предупреждением в логе, как и параметры, переданные вместе с источником. +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его +// временем жизни менеджер не управляет: закрывает источник тот, кто его создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. -// - Тип - Устарел. Тип класса, реализующего интерфейс Коннектор. -// УстарелСтрокаСоединения - Строка - Устарел. Строка соединения к БД, задается в источнике данных. -// УстарелПараметрыКоннектора - Массив - Устарел. Параметры коннектора, задаются в источнике данных. -// УстарелРазмерПулаСоединений - Число - Устарел. Размер пула соединений, задается в источнике данных. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // -Процедура ПриСозданииОбъекта( - Знач ИсточникДанных, - Знач УстарелСтрокаСоединения = "", - Знач УстарелПараметрыКоннектора = Неопределено, - Знач УстарелРазмерПулаСоединений = 10 -) +Процедура ПриСозданииОбъекта(Знач ИсточникДанных) ``` ### ДобавитьКлассВМодель @@ -140,12 +131,10 @@ ### Закрыть ```bsl -// Освобождает модель данных менеджера и закрывает источник данных, если его создал менеджер. +// Освобождает модель данных менеджера: после Закрыть() менеджер к работе непригоден. // -// Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей -// принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер -// создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его -// соединения закрыты. Модель данных очищается в любом случае, и к работе менеджер непригоден. +// Источник данных не закрывается: его соединения и хранилища сущностей принадлежат тому, кто +// источник создал, см. ИсточникДанных.Закрыть(). // Процедура Закрыть() ``` diff --git a/packagedef b/packagedef index f4ad496..61d3a33 100644 --- a/packagedef +++ b/packagedef @@ -4,7 +4,7 @@ // // BSLLS:CodeOutOfRegion-off Описание.Имя("entity") - .Версия("4.4.1.0") + .Версия("5.4.0.0") .Автор("Nikita Fedkin") .АдресАвтора("nixel2007@gmail.com") .Описание("entity") diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index eda4ef1..a25e58a 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -11,60 +11,32 @@ // ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; -// Булево - Источник создан самим менеджером по устаревшим параметрам соединения, и закрывать -// его должен менеджер. -Перем ВладеетИсточником; - // Лог - Журнал библиотеки. Перем Лог; // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного -// источника делят соединения и транзакции потоков. Создание по параметрам соединения устарело и -// отмечается предупреждением в логе, как и параметры, переданные вместе с источником. +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его +// временем жизни менеджер не управляет: закрывает источник тот, кто его создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. -// - Тип - Устарел. Тип класса, реализующего интерфейс Коннектор. -// УстарелСтрокаСоединения - Строка - Устарел. Строка соединения к БД, задается в источнике данных. -// УстарелПараметрыКоннектора - Массив - Устарел. Параметры коннектора, задаются в источнике данных. -// УстарелРазмерПулаСоединений - Число - Устарел. Размер пула соединений, задается в источнике данных. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // -Процедура ПриСозданииОбъекта( - Знач ИсточникДанных, - Знач УстарелСтрокаСоединения = "", - Знач УстарелПараметрыКоннектора = Неопределено, - Знач УстарелРазмерПулаСоединений = 10 -) +Процедура ПриСозданииОбъекта(Знач ИсточникДанных) Лог = Логирование.ПолучитьЛог("oscript.lib.entity.manager"); - Если ТипЗнч(ИсточникДанных) = Тип("ИсточникДанных") Тогда - Источник = ИсточникДанных; - ВладеетИсточником = Ложь; - ПредупредитьОЛишнихПараметрах( - УстарелСтрокаСоединения, - УстарелПараметрыКоннектора, - УстарелРазмерПулаСоединений - ); - Иначе - Лог.Предупреждение( - "Создание МенеджерСущностей по параметрам соединения устарело: создавайте менеджер из ИсточникДанных" - ); - Источник = Новый ИсточникДанных( - "", - ИсточникДанных, - УстарелСтрокаСоединения, - УстарелПараметрыКоннектора, - УстарелРазмерПулаСоединений - ); - ВладеетИсточником = Истина; - КонецЕсли; + Ожидаем.Что( + ТипЗнч(ИсточникДанных) = Тип("ИсточникДанных"), + "МенеджерСущностей создается из ИсточникДанных, а не из параметров соединения" + ).ЭтоИстина(); + + Источник = ИсточникДанных; Лог.Отладка("Инициализация менеджера сущностей с коннектором %1", Источник.ТипКоннектора()); @@ -72,37 +44,6 @@ КонецПроцедуры -// Предупреждает, что вместе с источником данных переданы устаревшие параметры конструктора. -// -// Параметры: -// СтрокаСоединения - Строка - Второй параметр конструктора. -// ПараметрыКоннектора - Массив, Неопределено - Третий параметр конструктора. -// РазмерПулаСоединений - Число - Четвертый параметр конструктора. -// -Процедура ПредупредитьОЛишнихПараметрах(Знач СтрокаСоединения, Знач ПараметрыКоннектора, Знач РазмерПулаСоединений) - - ЕстьЛишние = ЗначениеЗаполнено(СтрокаСоединения) - ИЛИ ПараметрыКоннектора <> Неопределено - ИЛИ РазмерПулаСоединений <> РазмерПулаПоУмолчанию(); - - Если ЕстьЛишние Тогда - Лог.Предупреждение( - "При создании МенеджерСущностей из ИсточникДанных остальные параметры конструктора не используются: " - + "строка соединения, параметры коннектора и размер пула берутся из источника" - ); - КонецЕсли; - -КонецПроцедуры - -// Размер пула, подразумеваемый конструктором, когда он не задан. -// -// Возвращаемое значение: -// Число - Значение по умолчанию параметра УстарелРазмерПулаСоединений. -// -Функция РазмерПулаПоУмолчанию() - Возврат 10; // BSLLS:MagicNumber-off -КонецФункции - // Регистрирует переданный тип класса-сценария в модели данных. // // Параметры: @@ -270,19 +211,13 @@ КонецПроцедуры -// Освобождает модель данных менеджера и закрывает источник данных, если его создал менеджер. +// Освобождает модель данных менеджера: после Закрыть() менеджер к работе непригоден. // -// Источник, переданный в конструктор, не закрывается: его соединения и хранилища сущностей -// принадлежат тому, кто источник создал, см. ИсточникДанных.Закрыть(). Источник, который менеджер -// создал сам по устаревшим параметрам соединения, менеджер и закрывает: после Закрыть() его -// соединения закрыты. Модель данных очищается в любом случае, и к работе менеджер непригоден. +// Источник данных не закрывается: его соединения и хранилища сущностей принадлежат тому, кто +// источник создал, см. ИсточникДанных.Закрыть(). // Процедура Закрыть() Экспорт - Если ВладеетИсточником Тогда - Источник.Закрыть(); - КонецЕсли; - МодельДанных.Очистить(); КонецПроцедуры diff --git "a/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" "b/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" index 9ecf5be..3e4901b 100644 --- "a/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" +++ "b/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" @@ -2,5 +2,5 @@ &Тест Процедура АбстрактныйКоннекторРеализуетИнтерфейсКоннектора() Экспорт - МенеджерСущностей = Новый МенеджерСущностей(Тип("АбстрактныйКоннектор")); // BSLLS:UnusedLocalVariable-off + Источник = Новый ИсточникДанных("Тесты", Тип("АбстрактныйКоннектор")); // BSLLS:UnusedLocalVariable-off КонецПроцедуры diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index d0ce547..2ae7dc7 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -275,8 +275,14 @@ ПараметрыКоннектора = Новый Массив; ПараметрыКоннектора.Добавить("ПроизвольныйПараметр"); - Менеджер = Новый МенеджерСущностей(Тип("КоннекторInMemory"), "проверка параметров", ПараметрыКоннектора); - // Массив вызывающего кода менеджеру не принадлежит: правка после создания до пула не доходит. + ИсточникПараметров = Новый ИсточникДанных( + "Проверка параметров", + Тип("КоннекторInMemory"), + "проверка параметров", + ПараметрыКоннектора + ); + Менеджер = Новый МенеджерСущностей(ИсточникПараметров); + // Массив вызывающего кода источнику не принадлежит: правка после создания до пула не доходит. ПараметрыКоннектора.Добавить("ЛишнийПараметр"); Попытка @@ -284,11 +290,11 @@ Ожидаем.Что(ПараметрыПула, "Параметры дошли до пула").ИмеетДлину(1); Ожидаем.Что(ПараметрыПула[0]).Равно("ПроизвольныйПараметр"); Исключение - Менеджер.Закрыть(); + ИсточникПараметров.Закрыть(); ВызватьИсключение; КонецПопытки; - Менеджер.Закрыть(); + ИсточникПараметров.Закрыть(); КонецПроцедуры diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 93ab2fe..54bff06 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -7,7 +7,7 @@ #Использовать ".." &Тест -Процедура РазмерПулаПоУмолчаниюСовпадаетСМенеджером() Экспорт +Процедура ИсточникСоздаетсяСоЗначениямиПоУмолчанию() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory")); @@ -15,7 +15,7 @@ Ожидаем.Что(Источник.ТипКоннектора()).Равно(Тип("КоннекторInMemory")); Ожидаем.Что(Источник.СтрокаСоединения()).Равно(""); Ожидаем.Что(Источник.ПараметрыКоннектора()).ИмеетДлину(0); - Ожидаем.Что(Источник.РазмерПула(), "Размер пула по умолчанию тот же, что у конструктора менеджера").Равно(10); + Ожидаем.Что(Источник.РазмерПула(), "Размер пула по умолчанию").Равно(10); КонецПроцедуры @@ -99,43 +99,19 @@ КонецПроцедуры +// Параметры соединения задает только источник: менеджер их не принимает и сам источник +// не создает. &Тест -Процедура ЛишниеПараметрыКонструктораПриИсточникеНеИспользуются() Экспорт +Процедура МенеджерБезИсточникаДанныхНеСоздается() Экспорт - ПараметрыИсточника = Новый Массив(); - ПараметрыИсточника.Добавить("из источника"); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", ПараметрыИсточника, 3); - - ЧужиеПараметры = Новый Массив(); - ЧужиеПараметры.Добавить("из конструктора"); - МенеджерСущностей = Новый МенеджерСущностей(Источник, "другая строка", ЧужиеПараметры, 7); - - Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); - Ожидаем.Что(Пул.СтрокаСоединения(), "Строка соединения взята из источника").Равно("источник"); - Ожидаем.Что(Пул.ПараметрыКоннектора(), "Параметры коннектора взяты из источника") - .Равно(Источник.ПараметрыКоннектора()); - Ожидаем.Что(Пул.РазмерПула(), "Размер пула взят из источника").Равно(3); - - МенеджерСущностей.Закрыть(); - Источник.Закрыть(); - -КонецПроцедуры - -&Тест -Процедура МенеджерСоздаетсяПоПолямИсточникаУстаревшимСпособом() Экспорт - - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", , 0); - - МенеджерСущностей = Новый МенеджерСущностей( - Источник.ТипКоннектора(), - Источник.СтрокаСоединения(), - Источник.ПараметрыКоннектора(), - Источник.РазмерПула() - ); - МенеджерСущностей.Инициализировать(); - - Ожидаем.Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + ТекстОшибки = ""; + Попытка + МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory")); // BSLLS:UnusedLocalVariable-off + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; - МенеджерСущностей.Закрыть(); + Ожидаем.Что(ТекстОшибки, "Менеджер отвергает параметры соединения вместо источника") + .Содержит("создается из ИсточникДанных"); КонецПроцедуры diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" index c8749b7..c9a30ad 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" @@ -389,7 +389,7 @@ ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -413,7 +413,7 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -440,7 +440,7 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -465,7 +465,7 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -490,7 +490,7 @@ &Тест Процедура РаботаСИменованнымПолем() Экспорт // Дано - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory")); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory"))); МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьСИменованнымПолем")); МенеджерСущностей.Инициализировать(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" index a2f141a..eedd37c 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" @@ -322,7 +322,7 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -348,7 +348,7 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -377,7 +377,7 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -404,7 +404,7 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index 4b4afc6..0450060 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -6,6 +6,7 @@ #Использовать "utils" Перем МенеджерСущностей; +Перем Источник; Перем Коннектор; &ВключенЕсли("() -> ЗапускатьТестыPostgreSQL()") @@ -50,7 +51,10 @@ ИмяБД, Порт ); - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторPostgreSQL"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник не должен закрыться с потоком подготовки. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.Инициализировать(); // Тесты коннектора работают собственным экземпляром: соединения из пула принадлежат пулу. @@ -66,6 +70,7 @@ ТестовыеУтилиты.УдалитьТаблицыВБазеДанных(Коннектор); Коннектор.Закрыть(); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры &Тест diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index 9fc47ff..06d5b3c 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -7,6 +7,7 @@ #Использовать "utils" Перем МенеджерСущностей; +Перем Источник; Перем Коннектор; &ВключенЕсли("() -> ЗапускатьТестыSQLite()") @@ -32,7 +33,10 @@ СтрокаСоединения = "Data Source=:memory:"; // СтрокаСоединения = "Data Source=test.db"; - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторSQLite"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник не должен закрыться с потоком подготовки. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.ДобавитьКлассВМодель(Тип("АвтоинкрементныйКлючБезКолонок")); МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); @@ -50,6 +54,7 @@ Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры &Тест diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 70ee610..edaa29a 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -76,21 +76,6 @@ КонецПроцедуры -// Менеджер, созданный по устаревшим параметрам соединения, сам создает источник, поэтому сам его -// и закрывает: старый код рассчитывает, что Закрыть() закрывает соединения. -&Тест -Процедура ЗакрытиеМенеджераЗакрываетСозданныйИмИсточник() Экспорт - - Менеджер = Новый МенеджерСущностей(Тип("КоннекторInMemory"), "неявный источник"); - Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); - - Менеджер.Закрыть(); - - Ожидаем.Что(Менеджер.ИсточникДанных().Закрыт(), "Источник, созданный менеджером, закрыт вместе с ним") - .ЭтоИстина(); - -КонецПроцедуры - &Тест Процедура ПослеЗакрытияИсточникаМенеджерНеРаботает() Экспорт diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index bfeb2d8..ff9c2a5 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -142,17 +142,24 @@ &Тест Процедура НулевойРазмерОтключаетПереиспользование() Экспорт - Менеджер = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияТестовойБазы, Неопределено, 0); + ИсточникБезПереиспользования = Новый ИсточникДанных( + "Без переиспользования", + ТипКоннектораТестовойБазы, + СтрокаСоединенияТестовойБазы, + , + 0 + ); + Менеджер = Новый МенеджерСущностей(ИсточникБезПереиспользования); Попытка ОчиститьТестовуюБазу(Менеджер); ПроверитьРаботуБезПереиспользования(Менеджер); Исключение - Менеджер.Закрыть(); + ИсточникБезПереиспользования.Закрыть(); ВызватьИсключение; КонецПопытки; - Менеджер.Закрыть(); + ИсточникБезПереиспользования.Закрыть(); КонецПроцедуры diff --git "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 1de6d55..e746521 100644 --- "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -217,11 +217,12 @@ КонецПроцедуры -// Создает менеджер, работает с сущностью и закрывает его. +// Создает источник и менеджер, работает с сущностью и закрывает их. // Процедура ПрожитьЦиклМенеджера() - Менеджер = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + ИсточникЦикла = Новый ИсточникДанных("Цикл", ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + Менеджер = Новый МенеджерСущностей(ИсточникЦикла); Попытка @@ -234,10 +235,12 @@ Исключение Менеджер.Закрыть(); + ИсточникЦикла.Закрыть(); ВызватьИсключение; КонецПопытки; Менеджер.Закрыть(); + ИсточникЦикла.Закрыть(); КонецПроцедуры From 333c6ad630c010fb6ba94ae6992b1be303c3c1d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 15:16:46 +0000 Subject: [PATCH 15/55] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=D0=B0=D1=85=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D0=BD=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sonar требует описание у переменной модуля: добавлено для Источник в наборах SQLite и PostgreSQL. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" | 1 + ...275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" | 1 + 2 files changed, 2 insertions(+) diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index 0450060..d050c33 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -6,6 +6,7 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник менеджера набора: создается в подготовке, закрывается после теста. Перем Источник; Перем Коннектор; diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index 06d5b3c..eb638e9 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -7,6 +7,7 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник менеджера набора: создается в подготовке, закрывается после теста. Перем Источник; Перем Коннектор; From cc4a1769f93fae189ea9958eba1b34c128f5ce39 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 15:19:23 +0000 Subject: [PATCH 16/55] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D1=80=D1=8B=D0=B2=D0=B0=D1=8E=D1=82=20=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D1=8F=D0=B2=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Менеджер источником не владеет, поэтому наборы, создающие менеджер в теле теста, закрывают источник сами: InMemory и JSON держат его в переменной набора и закрывают в ПослеКаждого, тест абстрактного коннектора - сразу после проверки интерфейса. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\265\320\272\321\202\320\276\321\200.os" | 3 ++- ...20\272\321\202\320\276\321\200InMemory.os" | 21 ++++++++++++++----- ...65\320\272\321\202\320\276\321\200JSON.os" | 18 ++++++++++++---- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git "a/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" "b/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" index 3e4901b..d625b80 100644 --- "a/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" +++ "b/tests/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" @@ -2,5 +2,6 @@ &Тест Процедура АбстрактныйКоннекторРеализуетИнтерфейсКоннектора() Экспорт - Источник = Новый ИсточникДанных("Тесты", Тип("АбстрактныйКоннектор")); // BSLLS:UnusedLocalVariable-off + Источник = Новый ИсточникДанных("Тесты", Тип("АбстрактныйКоннектор")); + Источник.Закрыть(); КонецПроцедуры diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" index c9a30ad..a47168c 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" @@ -9,6 +9,8 @@ Перем Коннектор; Перем СтрокаСоединения; +// ИсточникДанных - Источник менеджера, созданного в теле теста: закрывается после теста. +Перем ИсточникТеста; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -24,6 +26,10 @@ &ПослеКаждого Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); + Если ИсточникТеста <> Неопределено Тогда + ИсточникТеста.Закрыть(); + ИсточникТеста = Неопределено; + КонецЕсли; КонецПроцедуры &Тест @@ -389,7 +395,8 @@ ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -413,7 +420,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -440,7 +448,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -465,7 +474,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора))); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -490,7 +500,8 @@ &Тест Процедура РаботаСИменованнымПолем() Экспорт // Дано - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory"))); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory")); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьСИменованнымПолем")); МенеджерСущностей.Инициализировать(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" index eedd37c..7a08156 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" @@ -13,6 +13,8 @@ // Объект модели, разделяемый главным потоком и фоновым заданием // в тесте "ЧтениеНеКонфликтуетСПараллельнойЗаписью". Перем ОбъектМоделиФоновойЗаписи; +// ИсточникДанных - Источник менеджера, созданного в теле теста: закрывается после теста. +Перем ИсточникТеста; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -29,6 +31,10 @@ &ПослеКаждого Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); + Если ИсточникТеста <> Неопределено Тогда + ИсточникТеста.Закрыть(); + ИсточникТеста = Неопределено; + КонецЕсли; УдалитьФайлы(СтрокаСоединения); КонецПроцедуры @@ -322,7 +328,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -348,7 +355,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -377,7 +385,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -404,7 +413,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); From 633f721a5700378f743fbf1081290619c1e32f01 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 18:25:46 +0000 Subject: [PATCH 17/55] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B0=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=BF=D1=80=D0=B8=D0=BD=D0=B0=D0=B4=D0=BB=D0=B5?= =?UTF-8?q?=D0=B6=D0=B0=D1=82=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5?= =?UTF-8?q?=D1=80=D1=83,=20=D0=B0=20=D0=BD=D0=B5=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Хранилище работает моделью данных того менеджера, который его создал, поэтому и принадлежит менеджеру. Глобальный на процесс кэш хранилищ, ключуемый источником, заменен набором хранилищ внутри менеджера: ХранилищаСущностей стал классом с моделью и источником менеджера. До этого хранилище с моделью закрытого менеджера доставалось следующему менеджеру того же источника, и разыменование ссылки в нем уже не находило тип в очищенной модели. Тест ЗакрытиеМенеджераНеЛомаетДругойМенеджер Источника воспроизводил дефект. Источник остается чистым источником соединений: о сущностях он не знает, его закрытие хранилищ не касается, и проверка закрытости осталась в одном месте. Закрытие менеджера закрывает его хранилища и очищает модель. Операции внутреннего модуля принимают набор хранилищ вместо источника: через него же ищутся соседние хранилища при чтении ссылок. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 2 +- ...20\260\320\275\320\275\321\213\321\205.md" | 6 +- ...20\276\321\201\321\202\320\265\320\271.md" | 8 +- ...21\202\320\260\320\241\320\221\320\224.md" | 4 +- ...20\276\321\201\321\202\320\265\320\271.md" | 8 +- ...20\276\321\201\321\202\320\265\320\271.os" | 115 ++++++++++++++++ ...20\276\321\200\320\260\320\274\320\270.os" | 65 +++++---- ...20\276\321\201\321\202\320\265\320\271.os" | 123 ------------------ ...20\260\320\275\320\275\321\213\321\205.os" | 15 +-- ...20\276\321\201\321\202\320\265\320\271.os" | 26 ++-- ...20\276\321\201\321\202\320\265\320\271.os" | 33 ++--- ...21\207\320\275\320\270\320\272\320\260.os" | 66 ++++------ ...20\260\320\275\320\275\321\213\321\205.os" | 105 ++++++++++++--- 13 files changed, 315 insertions(+), 261 deletions(-) create mode 100644 "src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" delete mode 100644 "src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" diff --git a/README.md b/README.md index 4690af8..3ce718b 100644 --- a/README.md +++ b/README.md @@ -505,7 +505,7 @@ В отличие от МенеджераСущностей, ХранилищеСущностей не требует передачи в методы параметра "ТипСущности". -Хранилища сущностей и пулы сущностей совпадают в рамках одного типа сущности и одного источника данных. Другими словами, два менеджера сущностей, созданные из одного источника данных, вернут одинаковые хранилища сущностей одного типа. +Хранилища сущностей и пулы сущностей принадлежат менеджеру сущностей: хранилище работает моделью данных того менеджера, который его создал, и закрывается вместе с ним. Один менеджер на один тип сущности всегда возвращает одно и то же хранилище, а два менеджера, созданные из одного источника данных, возвращают разные хранилища со своими пулами сущностей. Соединения при этом общие: их дает пул источника. diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index 63f5fcb..2fccc4e 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -6,8 +6,10 @@ Пул создается вместе с источником и общий для всех, кто работает через источник; соединения пул открывает при первом обращении. Закрывает пул сам источник: методом `Закрыть()` или по завершению потока, в котором источник создан, если автозакрытие не выключено, см. -`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник не выдает ни соединений, -ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса +`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник соединений больше не выдает, +поэтому любая операция через него завершается ошибкой. О сущностях источник не знает: модель +данных и хранилища принадлежат менеджеру сущностей, а закрытие источника их не касается. +Тип коннектора и размер пула проверяются при создании: тип без интерфейса `АбстрактныйКоннектор` и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. ```bsl diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 7847275..153716f 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -131,10 +131,12 @@ ### Закрыть ```bsl -// Освобождает модель данных менеджера: после Закрыть() менеджер к работе непригоден. +// Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер +// к работе непригоден. // -// Источник данных не закрывается: его соединения и хранилища сущностей принадлежат тому, кто -// источник создал, см. ИсточникДанных.Закрыть(). +// Источник данных не закрывается: его соединения принадлежат тому, кто источник создал, +// см. ИсточникДанных.Закрыть(). Хранилища других менеджеров того же источника не затрагиваются: +// каждый менеджер владеет своими. // Процедура Закрыть() ``` diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 8496a05..6bd53a9 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -88,8 +88,8 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" ``` Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника делят -один пул, поэтому предел общий для источника. Хранилище работает через источник, из которого -создано, и своих соединений не держит. +один пул, поэтому предел общий для источника. Хранилище своих соединений не держит: их дает +источник менеджера, которому хранилище принадлежит. ### Незавершенные транзакции diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 9e7f7d3..82a1d01 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -3,15 +3,15 @@ ### ПриСозданииОбъекта ```bsl -// Создает хранилище, привязанное к типу сущности и работающее через переданный источник данных. +// Создает хранилище, привязанное к типу сущности. // -// Создается пулом хранилищ сущностей, напрямую конструировать не следует. +// Создается набором хранилищ менеджера, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ПИсточник - ИсточникДанных - Источник, соединениями которого выполняются операции. +// ПХранилища - ХранилищаСущностей - Хранилища менеджера, создавшего это хранилище. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПИсточник) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПХранилища) ``` ### СоздатьЭлемент diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" new file mode 100644 index 0000000..aa7a837 --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -0,0 +1,115 @@ +// Хранилища сущностей одного менеджера: соответствие типа сущности хранилищу. +// +// Хранилище работает моделью данных того менеджера, который его создал, поэтому и принадлежит +// менеджеру: иначе хранилище с моделью закрытого менеджера досталось бы следующему. Источник +// данных о сущностях не знает, он дает только соединения. +// +// Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, притом из +// разных потоков, поэтому попадание в кэш читается под блокировкой чтения: сериализуется только +// создание. +// +#Использовать "../concurrent" + +// ИсточникДанных - Источник, соединениями которого работают хранилища. +Перем пИсточник; +// МодельДанных - Модель данных менеджера, которому принадлежат хранилища. +Перем пМодельДанных; +// Соответствие - Тип сущности: хранилище этого типа. +Перем пХранилища; +// БлокировкаЧтенияЗаписи - Защищает набор: менеджером пользуются из разных потоков. +Перем пБлокировка; + +// Набор хранилищ одного менеджера. +// +// Параметры: +// Источник - ИсточникДанных - Источник, соединениями которого работают хранилища. +// МодельДанных - МодельДанных - Модель данных менеджера. +// +Процедура ПриСозданииОбъекта(Знач Источник, Знач МодельДанных) + + пИсточник = Источник; + пМодельДанных = МодельДанных; + пХранилища = Новый Соответствие(); + пБлокировка = Новый БлокировкаЧтенияЗаписи(); + +КонецПроцедуры + +// Источник данных, соединениями которого работают хранилища. +// +// Возвращаемое значение: +// ИсточникДанных - Источник данных. +// +Функция Источник() Экспорт + Возврат пИсточник; +КонецФункции + +// Возвращает хранилище сущностей, создавая его при первом обращении. +// +// К БД обращения нет, поэтому закрытый источник хранилище выдает: ошибку дает первая операция, +// когда хранилище просит соединение. +// +// Параметры: +// ТипСущности - Тип - Тип сущности, зарегистрированный в модели данных менеджера. +// +// Возвращаемое значение: +// ХранилищеСущностей - Хранилище для переданного типа. +// +Функция Получить(Знач ТипСущности) Экспорт + + пБлокировка.Чтение().Заблокировать(); + Хранилище = пХранилища.Получить(ТипСущности); + пБлокировка.Чтение().Разблокировать(); + + Если Хранилище <> Неопределено Тогда + Возврат Хранилище; + КонецЕсли; + + пБлокировка.Запись().Заблокировать(); + + Попытка + + // Пока ждали запись, хранилище мог создать другой поток. + Хранилище = пХранилища.Получить(ТипСущности); + Если Хранилище = Неопределено Тогда + Хранилище = Новый ХранилищеСущностей(пМодельДанных.Получить(ТипСущности), ЭтотОбъект); + пХранилища.Вставить(ТипСущности, Хранилище); + КонецЕсли; + + Исключение + + пБлокировка.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + пБлокировка.Запись().Разблокировать(); + + Возврат Хранилище; + +КонецФункции + +// Закрывает хранилища менеджера и убирает их из набора. +// +Процедура Закрыть() Экспорт + + пБлокировка.Запись().Заблокировать(); + + Попытка + + Для Каждого КлючИЗначение Из пХранилища Цикл + КлючИЗначение.Значение.Закрыть(); + КонецЦикла; + пХранилища.Очистить(); + + Исключение + + пБлокировка.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + пБлокировка.Запись().Разблокировать(); + +КонецПроцедуры diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index a854d43..aedd513 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -20,18 +20,18 @@ // Сохраняет сущность в БД соединением источника данных. // // Параметры: -// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Сохраняемая сущность. // -Процедура Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность) Экспорт ПроверитьЧтоКлассЯвляетсяСущностью(АктивнаяЗапись.ТипСущности(Сущность)); ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); - Пул = Источник.ПулСоединений(); + Пул = Хранилища.Источник().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -52,7 +52,7 @@ // Ищет сущности соединением источника данных. // // Параметры: -// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ОпцииПоиска - Соответствие, ОпцииПоиска - Опции поиска. @@ -60,12 +60,12 @@ // Возвращаемое значение: // Массив - Найденные сущности. // -Функция Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ОпцииПоиска); НайденныеСущности = Новый Массив; - Пул = Источник.ПулСоединений(); + Пул = Хранилища.Источник().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -74,7 +74,7 @@ НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл - НайденныеСущности.Добавить(СобратьСущность(Коннектор, Источник, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); + НайденныеСущности.Добавить(СобратьСущность(Коннектор, Хранилища, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); КонецЦикла; Исключение @@ -91,7 +91,7 @@ // Ищет одну сущность соединением источника данных. // // Параметры: -// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ОпцииПоиска - Соответствие, ОпцииПоиска, Произвольный - Опции поиска; любой другой тип - идентификатор. @@ -100,7 +100,7 @@ // Произвольный - Найденная сущность. // Неопределено - Сущность не найдена. // -Функция ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") ИЛИ ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда ПередаваемыеОпцииПоиска = ОпцииПоиска; @@ -109,7 +109,7 @@ ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); КонецЕсли; - НайденныеСущности = Получить(Источник, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); + НайденныеСущности = Получить(Хранилища, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); Если НайденныеСущности.Количество() = 0 Тогда Возврат Неопределено; @@ -123,14 +123,14 @@ // Сущность должна иметь заполненный идентификатор. // // Параметры: -// Источник - ИсточникДанных - Источник, соединением которого выполняется операция. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Удаляемая сущность. // -Процедура Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность) Экспорт - Пул = Источник.ПулСоединений(); + Пул = Хранилища.Источник().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -372,17 +372,17 @@ // Возвращаемое значение: // Произвольный - Заполненная сущность. // -Функция СобратьСущность(Коннектор, Источник, ОбъектМодели, ПулСущностей, НайденнаяСтрока) +Функция СобратьСущность(Коннектор, Хранилища, ОбъектМодели, ПулСущностей, НайденнаяСтрока) ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), ОбъектМодели.Идентификатор().ИмяПоля ); - Сущность = ПолучитьСущностьИзПула(Источник, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); + Сущность = ПолучитьСущностьИзПула(Хранилища, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); - ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Источник, ОбъектМодели, ЗначениеИдентификатора); - ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Источник, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Хранилища, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Хранилища, ОбъектМодели, ЗначениеИдентификатора); Возврат Сущность; @@ -457,7 +457,7 @@ // Возвращает экземпляр сущности из пула сущностей, создавая его при первом обращении. // // Параметры: -// Источник - ИсточникДанных - Источник, которому принадлежит хранилище сущности. +// Хранилища - ХранилищаСущностей - Хранилища менеджера, которому принадлежит хранилище сущности. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ЗначениеИдентификатора - Произвольный - Идентификатор сущности. @@ -465,14 +465,14 @@ // Возвращаемое значение: // Произвольный - Экземпляр сущности. // -Функция ПолучитьСущностьИзПула(Источник, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) +Функция ПолучитьСущностьИзПула(Хранилища, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) Сущность = ПулСущностей.ПолучитьИлиУмолчание(ЗначениеИдентификатора, Неопределено); Если Сущность <> Неопределено Тогда Возврат Сущность; КонецЕсли; - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Источник); + ХранилищеСущностей = Хранилища.Получить(ОбъектМодели.ТипСущности()); Сущность = АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей); // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться @@ -489,11 +489,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. -// Источник - ИсточникДанных - Источник, соединением которого читаются ссылки. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Источник, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Хранилища, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого Колонка Из ОбъектМодели.Колонки() Цикл @@ -503,7 +503,7 @@ ЗначениеКолонки = РазыменоватьСсылку( ЗначениеКолонки, Колонка.ТипСсылки, - Источник, + Хранилища, ОбъектМодели, Сущность, ЗначениеИдентификатора @@ -524,7 +524,7 @@ // Параметры: // ЗначениеКолонки - Произвольный - Идентификатор сущности, на которую указывает ссылка. // ТипСсылки - Тип - Тип сущности, на которую указывает ссылка. -// Источник - ИсточникДанных - Источник, которому принадлежит хранилище сущности по ссылке. +// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищется хранилище ссылки. // ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит колонка. // Сущность - Произвольный - Сущность, которой принадлежит колонка. // ЗначениеИдентификатора - Произвольный - Идентификатор этой сущности. @@ -535,7 +535,7 @@ Функция РазыменоватьСсылку( Знач ЗначениеКолонки, Знач ТипСсылки, - Источник, + Хранилища, ОбъектМодели, Сущность, Знач ЗначениеИдентификатора @@ -545,7 +545,7 @@ Возврат Сущность; КонецЕсли; - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипСсылки), Источник); + ХранилищеСущностейСсылки = Хранилища.Получить(ТипСсылки); Возврат ХранилищеСущностейСсылки.ПолучитьОдно(ЗначениеКолонки); @@ -556,11 +556,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Источник - ИсточникДанных - Источник, которому принадлежат хранилища элементов коллекций. +// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищутся хранилища элементов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Источник, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Хранилища, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл @@ -576,7 +576,7 @@ ЗначениеКолонки = ОбработкаКоллекций.ПреобразоватьКоллекциюСтрокККоллекции(Строки, ПодчиненнаяТаблица); Если ПодчиненнаяТаблица.КаскадноеЧтение И ТипыКолонок.ЭтоСсылочныйТип(ПодчиненнаяТаблица.ТипЭлемента) Тогда - ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Источник, ОбъектМодели); + ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Хранилища); КонецЕсли; ОбъектМодели.УстановитьЗначениеПодчиненнойТаблицыВПоле(Сущность, ПодчиненнаяТаблица.ИмяПоля, ЗначениеКолонки); @@ -590,12 +590,11 @@ // Параметры: // Коллекция - Массив - Коллекция идентификаторов, изменяется на месте. // ТипЭлемента - Тип - Тип сущности элементов коллекции. -// Источник - ИсточникДанных - Источник, которому принадлежит хранилище элементов. -// ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит коллекция. +// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищется хранилище элементов. // -Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Источник, ОбъектМодели) +Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Хранилища) - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипЭлемента), Источник); + ХранилищеСущностейСсылки = Хранилища.Получить(ТипЭлемента); Для НомерИндекса = 0 По Коллекция.ВГраница() Цикл Коллекция[НомерИндекса] = ХранилищеСущностейСсылки.ПолучитьОдно(Коллекция[НомерИндекса]); diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" deleted file mode 100644 index cd5da93..0000000 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ /dev/null @@ -1,123 +0,0 @@ -// Пул хранилищ сущностей: соответствие источника данных и типа сущности хранилищу, общее на процесс. -// -// Ключ - источник данных, поэтому хранилища разных источников не смешиваются, а менеджеры одного -// источника делят хранилища. Обращения идут на каждую прочитанную сущность и на каждое -// разыменование ссылки, притом из разных потоков, поэтому попадание в кэш читается под -// блокировкой чтения: сериализуется только создание. -// -#Использовать "../concurrent" - -// Соответствие - Источник данных: соответствие типа сущности хранилищу. -Перем Хранилища; -// БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. -Перем Блокировка; - -// Возвращает хранилище сущностей, создавая его при первом обращении. Для закрытого источника -// хранилище не создается: обращение завершается ошибкой. -// -// Параметры: -// ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. -// Источник - ИсточникДанных - Источник, соединениями которого хранилище выполняет операции. -// -// Возвращаемое значение: -// ХранилищеСущностей - Хранилище для переданной модели. -// -Функция Получить(ОбъектМодели, Источник) Экспорт - - ТипСущности = ОбъектМодели.ТипСущности(); - - Блокировка.Чтение().Заблокировать(); - Хранилище = НайтиХранилище(Источник, ТипСущности); - Блокировка.Чтение().Разблокировать(); - - Если Хранилище <> Неопределено Тогда - Возврат Хранилище; - КонецЕсли; - - Блокировка.Запись().Заблокировать(); - - Попытка - - // Пока ждали запись, хранилище мог создать другой поток. - Хранилище = НайтиХранилище(Источник, ТипСущности); - Если Хранилище = Неопределено Тогда - // Закрытый источник свои хранилища уже убрал: новое легло бы в кэш навсегда. - Если Источник.Закрыт() Тогда - ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", Источник.Имя()); - КонецЕсли; - ХранилищаИсточника = Хранилища.Получить(Источник); - Если ХранилищаИсточника = Неопределено Тогда - ХранилищаИсточника = Новый Соответствие; - Хранилища.Вставить(Источник, ХранилищаИсточника); - КонецЕсли; - Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Источник); - ХранилищаИсточника.Вставить(ТипСущности, Хранилище); - КонецЕсли; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - - Возврат Хранилище; - -КонецФункции - -// Закрывает и убирает из пула хранилища указанного источника данных. -// -// Параметры: -// Источник - ИсточникДанных - Закрываемый источник данных. -// -Процедура Закрыть(Источник) Экспорт - - Блокировка.Запись().Заблокировать(); - - Попытка - - ХранилищаИсточника = Хранилища.Получить(Источник); - Если ХранилищаИсточника <> Неопределено Тогда - Для Каждого КлючИЗначение Из ХранилищаИсточника Цикл - КлючИЗначение.Значение.Закрыть(); - КонецЦикла; - Хранилища.Удалить(Источник); - КонецЕсли; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - -КонецПроцедуры - -// Ищет хранилище в пуле. Вызывается при захваченной блокировке. -// -// Параметры: -// Источник - ИсточникДанных - Источник данных. -// ТипСущности - Тип - Тип сущности. -// -// Возвращаемое значение: -// ХранилищеСущностей, Неопределено - Хранилище или Неопределено, если его еще нет. -// -Функция НайтиХранилище(Источник, ТипСущности) - - ХранилищаИсточника = Хранилища.Получить(Источник); - Если ХранилищаИсточника = Неопределено Тогда - Возврат Неопределено; - КонецЕсли; - - Возврат ХранилищаИсточника.Получить(ТипСущности); - -КонецФункции - -Хранилища = Новый Соответствие(); -Блокировка = Новый БлокировкаЧтенияЗаписи(); diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 0b33f19..2876b01 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -43,8 +43,10 @@ // Пул создается вместе с источником и общий для всех, кто работает через источник; соединения // пул открывает при первом обращении. Закрывает пул сам источник: методом Закрыть() или по // завершению потока, в котором источник создан, если автозакрытие не выключено, см. -// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник не выдает ни соединений, -// ни хранилищ сущностей. Тип коннектора и размер пула проверяются при создании: тип без интерфейса +// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник соединений больше не выдает, +// поэтому любая операция через него завершается ошибкой. О сущностях источник не знает: модель +// данных и хранилища принадлежат менеджеру сущностей, а закрытие источника их не касается. +// Тип коннектора и размер пула проверяются при создании: тип без интерфейса // АбстрактныйКоннектор и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. // // Параметры: @@ -187,17 +189,14 @@ // Процедура Закрыть() Экспорт - // Признак переключается атомарно и до уборки хранилищ: закрытие выполняется один раз, а кэш - // хранилищ, проверяющий признак под своей блокировкой, после уборки нового хранилища - // закрытому источнику уже не создаст. Своей блокировки поперек вызовов в кэш и пул источник - // не держит: они потокобезопасны сами, а вложенное ожидание вело бы к взаимной блокировке. + // Признак переключается атомарно: закрытие выполняется один раз. Своей блокировки поперек + // вызова в пул источник не держит: пул потокобезопасен сам, а вложенное ожидание вело бы + // к взаимной блокировке. Если НЕ пЗакрыт.СравнитьИУстановить(Ложь, Истина) Тогда Возврат; КонецЕсли; Попытка - // Хранилища сущностей работают через источник: закрываются вместе с ним. - ХранилищаСущностей.Закрыть(ЭтотОбъект); пПул.Закрыть(); Исключение пЗакрыт.Установить(Ложь); diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index a25e58a..4663465 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -11,6 +11,10 @@ // ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; +// ХранилищаСущностей - Хранилища менеджера по типам сущностей. Принадлежат менеджеру, потому что +// работают его моделью данных. +Перем Хранилища; + // Лог - Журнал библиотеки. Перем Лог; @@ -41,6 +45,7 @@ Лог.Отладка("Инициализация менеджера сущностей с коннектором %1", Источник.ТипКоннектора()); МодельДанных = Новый МодельДанных; + Хранилища = Новый ХранилищаСущностей(Источник, МодельДанных); КонецПроцедуры @@ -135,7 +140,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -162,7 +167,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -190,7 +195,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -207,17 +212,20 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры -// Освобождает модель данных менеджера: после Закрыть() менеджер к работе непригоден. +// Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер +// к работе непригоден. // -// Источник данных не закрывается: его соединения и хранилища сущностей принадлежат тому, кто -// источник создал, см. ИсточникДанных.Закрыть(). +// Источник данных не закрывается: его соединения принадлежат тому, кто источник создал, +// см. ИсточникДанных.Закрыть(). Хранилища других менеджеров того же источника не затрагиваются: +// каждый менеджер владеет своими. // Процедура Закрыть() Экспорт + Хранилища.Закрыть(); МодельДанных.Очистить(); КонецПроцедуры @@ -308,9 +316,7 @@ // ХранилищеСущностей - Хранилище сущностей, привязанное к переданному типу сущности. // Функция ПолучитьХранилищеСущностей(ТипСущности) Экспорт - ОбъектМодели = МодельДанных.Получить(ТипСущности); - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Источник); - Возврат ХранилищеСущностей; + Возврат Хранилища.Получить(ТипСущности); КонецФункции // @internal diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index b2d56f5..d2f52df 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -9,22 +9,23 @@ // Карта разделяется всеми потоками исполнения, поэтому синхронизированная. Перем ПулСущностей; -// ИсточникДанных - Источник, соединением которого выполняется каждая операция. -Перем Источник; +// ХранилищаСущностей - Хранилища менеджера, которому принадлежит это хранилище. Через них +// хранилище получает источник для операции и соседние хранилища при чтении ссылок. +Перем Хранилища; -// Создает хранилище, привязанное к типу сущности и работающее через переданный источник данных. +// Создает хранилище, привязанное к типу сущности. // -// Создается пулом хранилищ сущностей, напрямую конструировать не следует. +// Создается набором хранилищ менеджера, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ПИсточник - ИсточникДанных - Источник, соединениями которого выполняются операции. +// ПХранилища - ХранилищаСущностей - Хранилища менеджера, создавшего это хранилище. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПИсточник) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПХранилища) ОбъектМодели = ПОбъектМодели; ПулСущностей = Новый СинхронизированнаяКарта(Новый КартаСоответствие()); - Источник = ПИсточник; + Хранилища = ПХранилища; КонецПроцедуры @@ -47,7 +48,7 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для сохранения в БД. // Процедура Сохранить(Сущность) Экспорт - РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры // Осуществляет поиск сущностей типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -72,7 +73,7 @@ ОпцииПоиска = Новый ОпцииПоиска(); КонецЕсли; - Возврат РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -99,7 +100,7 @@ ОпцииПоиска = Новый ОпцииПоиска(); КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдно(Источник, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -113,7 +114,7 @@ // Сущность - Произвольный - Удаляемая сущность // Процедура Удалить(Сущность) Экспорт - РаботаСКоннекторами.Удалить(Источник, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры // Выполняет очистку текущего Хранилища сущностей. @@ -131,19 +132,19 @@ // сам менеджер сущностей. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Хранилища.Источник()); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Источник); + РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Хранилища.Источник()); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Хранилища.Источник()); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -164,7 +165,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннектором(Источник, Алгоритм, Контекст, Объект); + РаботаСКоннекторами.ВыполнитьСКоннектором(Хранилища.Источник(), Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -186,7 +187,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник, Алгоритм, Контекст, Объект); + Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Хранилища.Источник(), Алгоритм, Контекст, Объект); КонецФункции // Возвращает объект модели, связанный с текущим хранилищем сущностей. diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 83b04b9..c0318ee 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -11,62 +11,43 @@ #Использовать "../src/internal" #Использовать "utils" +// Хранилища принадлежат менеджеру: он выдает одно и то же хранилище на тип и закрывает свои +// хранилища сам. &Тест -Процедура КэшХранилищВыдаетХранилищеПоИсточнику() Экспорт +Процедура МенеджерВыдаетОдноХранилищеНаТип() Экспорт - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище по источнику"); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище менеджера"); Менеджер = МенеджерАвторов(Источник); - ОбъектМодели = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")); - Хранилище = ХранилищаСущностей.Получить(ОбъектМодели, Источник); + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); - Ожидаем.Что(Хранилище.Получить(), "Хранилище, полученное по источнику, работает").ИмеетДлину(0); - Ожидаем.Что(Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Менеджер выдает то же хранилище") + Ожидаем.Что(Хранилище.Получить(), "Хранилище менеджера работает").ИмеетДлину(0); + Ожидаем.Что(Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Повторное обращение дает то же хранилище") .ЭтоИстина(); Источник.Закрыть(); КонецПроцедуры +// Хранилище закрытого источника получить можно: к БД это обращение не идет. Ошибку дает первая +// же операция, когда хранилище просит у источника соединение. &Тест -Процедура КэшХранилищЗакрываетсяПоИсточнику() Экспорт - - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие по источнику"); - Менеджер = МенеджерАвторов(Источник); - ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); - - ХранилищаСущностей.Закрыть(Источник); - - Ожидаем.Что(ХранилищеДоЗакрытия = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Хранилище создано заново") - .ЭтоЛожь(); - - Источник.Закрыть(); - -КонецПроцедуры - -// Получение хранилища к БД не обращается, поэтому закрытый источник должен отказывать в нем сам: -// иначе хранилище закрытого источника легло бы в кэш, откуда его уже никто не уберет. -&Тест -Процедура ЗакрытыйИсточникХранилищНеВыдает() Экспорт - - ПодключитьКлассыСущностей(); +Процедура ОперацияХранилищаЗакрытогоИсточникаЗавершаетсяОшибкой() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище после закрытия"); - Менеджер = Новый МенеджерСущностей(Источник); - Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); - Менеджер.ДобавитьКлассВМодель(Тип("Автор")); - Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Менеджер = МенеджерАвторов(Источник); + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); Источник.Закрыть(); ТекстОшибки = ""; Попытка - Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Хранилище.Получить(); Исключение ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); КонецПопытки; - Ожидаем.Что(ТекстОшибки, "Хранилище закрытого источника не выдается и в кэш не попадает").Содержит("закрыт"); + Ожидаем.Что(ТекстОшибки, "Операция через закрытый источник завершается ошибкой").Содержит("закрыт"); КонецПроцедуры @@ -83,22 +64,25 @@ КонецПроцедуры +// Операция идет соединением источника, а метаданные и пул сущностей берутся у хранилища: оба +// принадлежат одному менеджеру, поэтому рассогласоваться не могут. &Тест -Процедура СущностьСохраняетсяИЧитаетсяЧерезИсточник() Экспорт +Процедура СущностьСохраняетсяИЧитаетсяЧерезХранилище() Экспорт - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "операции через источник"); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "операции через хранилище"); Менеджер = МенеджерАвторов(Источник); - ОбъектМодели = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")); - ПулСущностей = Менеджер.ПолучитьПулСущностей(Тип("Автор")); + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); Автор = Новый Автор; Автор.Имя = "Иван"; Автор.ВтороеИмя = "Иванов"; - РаботаСКоннекторами.Сохранить(Источник, ОбъектМодели, ПулСущностей, Автор); + Хранилище.Сохранить(Автор); - Авторы = РаботаСКоннекторами.Получить(Источник, ОбъектМодели, ПулСущностей, Новый ОпцииПоиска); - - Ожидаем.Что(Авторы, "Сохраненный автор прочитан через источник").ИмеетДлину(1); + Ожидаем.Что(Хранилище.Получить(), "Сохраненный автор прочитан хранилищем").ИмеетДлину(1); + Ожидаем + .Что(Хранилище.ПолучитьОбъектМодели() = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")), + "Хранилище работает объектом модели своего менеджера") + .ЭтоИстина(); Источник.Закрыть(); diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index edaa29a..b1bc633 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -124,27 +124,82 @@ КонецПроцедуры +// Хранилища принадлежат менеджеру, поэтому закрытие менеджера их и закрывает: прочитанные +// сущности из пула сущностей уходят. &Тест -Процедура ХранилищаНеПереживаютЗакрытиеИсточника() Экспорт +Процедура ЗакрытиеМенеджераЗакрываетЕгоХранилища() Экспорт - ПодключитьСценарий( - ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), - "СущностьБезГенерируемогоИдентификатора" - ); - ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + ПодключитьКлассыСущностей(); - Первый = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "кэш хранилищ"); - ХранилищеПервого = ХранилищеАвторов(Первый); - Первый.Закрыть(); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие хранилищ"); + Менеджер = МенеджерАвторов(Источник); - Второй = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "кэш хранилищ"); - ХранилищеВторого = ХранилищеАвторов(Второй); + Автор = Менеджер.СоздатьЭлемент(Тип("Автор")); + Автор.Имя = "Иван"; + Менеджер.Сохранить(Автор); + + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Ожидаем.Что(Хранилище.ПолучитьПулСущностей().Количество(), "Сохраненная сущность попала в пул сущностей") + .Равно(1); + + Менеджер.Закрыть(); + + Ожидаем.Что(Хранилище.ПолучитьПулСущностей().Количество(), "Закрытие менеджера очистило пул сущностей") + .Равно(0); + + Источник.Закрыть(); - Ожидаем.Что(ХранилищеПервого = ХранилищеВторого, "Хранилище закрытого источника не переиспользуется") +КонецПроцедуры + +// Хранилище работает моделью данных своего менеджера, поэтому и принадлежит менеджеру: у двух +// менеджеров одного источника хранилища разные, как и пулы сущностей в них. +&Тест +Процедура ХранилищаПринадлежатМенеджеруАНеИсточнику() Экспорт + + ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища по менеджеру"); + Первый = МенеджерАвторов(Источник); + Второй = МенеджерАвторов(Источник); + + Ожидаем + .Что(Первый.ПолучитьХранилищеСущностей(Тип("Автор")) = Второй.ПолучитьХранилищеСущностей(Тип("Автор")), + "У каждого менеджера свое хранилище") .ЭтоЛожь(); - Ожидаем.Что(ХранилищеВторого.Получить(), "Хранилище нового источника работает").ИмеетДлину(0); - Второй.Закрыть(); + Источник.Закрыть(); + +КонецПроцедуры + +// Закрытие менеджера очищает его модель данных. Пока хранилища принадлежали источнику, хранилище +// с моделью закрытого менеджера доставалось следующему менеджеру, и разыменование ссылки в нем +// уже не находило тип. +&Тест +Процедура ЗакрытиеМенеджераНеЛомаетДругойМенеджерИсточника() Экспорт + + ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "чужая модель"); + + Первый = МенеджерАвторов(Источник); + Внешняя = Первый.СоздатьЭлемент(Тип("СущностьБезГенерируемогоИдентификатора")); + Внешняя.ВнутреннийИдентификатор = 1; + Первый.Сохранить(Внешняя); + Автор = Первый.СоздатьЭлемент(Тип("Автор")); + Автор.Имя = "Иван"; + Автор.ВнешняяСущность = Внешняя; + Первый.Сохранить(Автор); + + Второй = МенеджерАвторов(Источник); + ХранилищеВторого = Второй.ПолучитьХранилищеСущностей(Тип("Автор")); + + Первый.Закрыть(); + + Ожидаем + .Что(ХранилищеВторого.Получить(), "Чтение со ссылкой переживает закрытие чужого менеджера") + .ИмеетДлину(1); + + Источник.Закрыть(); КонецПроцедуры @@ -162,24 +217,38 @@ КонецПроцедуры -// Хранилище авторов менеджера, созданного из источника. +// Менеджер с типом Автор в модели, созданный из источника. // // Параметры: // Источник - ИсточникДанных - Источник. // // Возвращаемое значение: -// ХранилищеСущностей - Хранилище сущностей типа Автор. +// МенеджерСущностей - Менеджер с инициализированной моделью. // -Функция ХранилищеАвторов(Источник) +Функция МенеджерАвторов(Источник) Менеджер = Новый МенеджерСущностей(Источник); + // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); Менеджер.ДобавитьКлассВМодель(Тип("Автор")); Менеджер.Инициализировать(); - Возврат Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Возврат Менеджер; КонецФункции +// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. +// +Процедура ПодключитьКлассыСущностей() + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + +КонецПроцедуры + // Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL // по переменным среды. // From f483d9030ddb5b3f30491f45bda4127fd849ab6e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 18:31:21 +0000 Subject: [PATCH 18/55] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D0=B0=20=D0=A1=D0=BE=D0=B1=D1=80=D0=B0=D1=82=D1=8C=D0=A1=D1=83?= =?UTF-8?q?=D1=89=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D1=80=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=BE=20=D0=BA=20=D1=81=D0=B8=D0=B3?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=D1=83=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sonar: в комментарии остался параметр Источник, которого в сигнатуре больше нет, и не было описания параметра Хранилища. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index aedd513..b29dd8e 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -364,7 +364,7 @@ // // Параметры: // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Источник - ИсточникДанных - Источник, соединением которого читаются ссылки и коллекции. +// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. From 96b88e9af7ea78fd3a62bb02cfda70b6023f8a0e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 18:44:06 +0000 Subject: [PATCH 19/55] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B5=20=D0=B4=D0=B5=D0=BB=D0=B5=D0=B3=D0=B8=D1=80?= =?UTF-8?q?=D1=83=D0=B5=D1=82=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=D1=83?= =?UTF-8?q?,=20=D0=B0=20=D0=BD=D0=B5=20=D0=BD=D0=B0=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Направление делегирования выбрано: менеджер - исполнитель, хранилище - тонкое представление типа. Пять операций над данными, транзакции и прямой доступ к БД хранилище больше не выполняет само, а подставляет свой тип сущности и вызывает менеджер. Поэтому промежуточный класс ХранилищаСущностей не нужен: набор хранилищ с блокировкой вернулся в менеджер, а внутренний модуль получает сам менеджер - источник он берет через ИсточникДанных(), соседние хранилища через ПолучитьХранилищеСущностей(). Заодно закрыта гонка из ревью: признак закрытия защищен той же блокировкой записи, поэтому хранилище, запрошенное после начала закрытия, уже не создается, а обращение к закрытому менеджеру завершается ошибкой. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 4 +- ...20\276\321\201\321\202\320\265\320\271.md" | 4 + ...21\202\320\260\320\241\320\221\320\224.md" | 5 +- ...20\276\321\201\321\202\320\265\320\271.md" | 6 +- ...20\276\321\201\321\202\320\265\320\271.os" | 115 ------------------ ...20\276\321\200\320\260\320\274\320\270.os" | 66 +++++----- ...20\276\321\201\321\202\320\265\320\271.os" | 96 +++++++++++++-- ...20\276\321\201\321\202\320\265\320\271.os" | 44 +++---- ...20\260\320\275\320\275\321\213\321\205.os" | 34 ++++++ 9 files changed, 183 insertions(+), 191 deletions(-) delete mode 100644 "src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" diff --git a/README.md b/README.md index 3ce718b..df902b8 100644 --- a/README.md +++ b/README.md @@ -501,10 +501,12 @@ ### ХранилищеСущностей -ХранилищеСущностей предоставляет тот же интерфейс по работе с сущностями и транзакциями, но с глобальной привязкой к конкретному типу сущности. Для получения ХранилищаСущностей служит метод `МенеджерСущностей::ПолучитьХранилищеСущностей`. +ХранилищеСущностей предоставляет тот же интерфейс по работе с сущностями и транзакциями, но с привязкой к конкретному типу сущности. Для получения ХранилищаСущностей служит метод `МенеджерСущностей::ПолучитьХранилищеСущностей`. В отличие от МенеджераСущностей, ХранилищеСущностей не требует передачи в методы параметра "ТипСущности". +Своей логики работы с БД у хранилища нет: оно подставляет свой тип сущности и делегирует операцию менеджеру, которому принадлежит. Поэтому поведение вызова через хранилище и через менеджер совпадает, в том числе для транзакций и прямого доступа к БД. + Хранилища сущностей и пулы сущностей принадлежат менеджеру сущностей: хранилище работает моделью данных того менеджера, который его создал, и закрывается вместе с ним. Один менеджер на один тип сущности всегда возвращает одно и то же хранилище, а два менеджера, созданные из одного источника данных, возвращают разные хранилища со своими пулами сущностей. Соединения при этом общие: их дает пул источника. diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 153716f..5dd57bb 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -225,6 +225,10 @@ ```bsl // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // +// Хранилище создается при первом обращении и живет до закрытия менеджера. К БД обращения нет, +// поэтому хранилище выдает и менеджер закрытого источника: ошибку дает первая операция, когда +// хранилище просит соединение. +// // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели // diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 6bd53a9..7109e00 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -144,8 +144,9 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" `ИсточникДанных.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. Тот же эффект дает завершение потока, в котором источник создан, если автозакрытие не выключено. -`МенеджерСущностей.Закрыть()` закрывает источник только тогда, когда сам его создал: при создании -менеджера по устаревшим параметрам соединения. +`МенеджерСущностей.Закрыть()` источник не трогает: менеджер его не создавал, а закрывает свои +хранилища и освобождает модель данных. Хранилища других менеджеров того же источника это не +затрагивает. Закрытие терминально на обоих уровнях. Закрытый источник соединений не выдает: обращение через него завершается ошибкой `Источник данных <Имя> закрыт`. Закрытый пул тоже: ожидающий, diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 82a1d01..007833d 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -5,13 +5,13 @@ ```bsl // Создает хранилище, привязанное к типу сущности. // -// Создается набором хранилищ менеджера, напрямую конструировать не следует. +// Создается менеджером сущностей, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ПХранилища - ХранилищаСущностей - Хранилища менеджера, создавшего это хранилище. +// ПМенеджер - МенеджерСущностей - Менеджер, создавший это хранилище. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПХранилища) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПМенеджер) ``` ### СоздатьЭлемент diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" deleted file mode 100644 index aa7a837..0000000 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ /dev/null @@ -1,115 +0,0 @@ -// Хранилища сущностей одного менеджера: соответствие типа сущности хранилищу. -// -// Хранилище работает моделью данных того менеджера, который его создал, поэтому и принадлежит -// менеджеру: иначе хранилище с моделью закрытого менеджера досталось бы следующему. Источник -// данных о сущностях не знает, он дает только соединения. -// -// Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, притом из -// разных потоков, поэтому попадание в кэш читается под блокировкой чтения: сериализуется только -// создание. -// -#Использовать "../concurrent" - -// ИсточникДанных - Источник, соединениями которого работают хранилища. -Перем пИсточник; -// МодельДанных - Модель данных менеджера, которому принадлежат хранилища. -Перем пМодельДанных; -// Соответствие - Тип сущности: хранилище этого типа. -Перем пХранилища; -// БлокировкаЧтенияЗаписи - Защищает набор: менеджером пользуются из разных потоков. -Перем пБлокировка; - -// Набор хранилищ одного менеджера. -// -// Параметры: -// Источник - ИсточникДанных - Источник, соединениями которого работают хранилища. -// МодельДанных - МодельДанных - Модель данных менеджера. -// -Процедура ПриСозданииОбъекта(Знач Источник, Знач МодельДанных) - - пИсточник = Источник; - пМодельДанных = МодельДанных; - пХранилища = Новый Соответствие(); - пБлокировка = Новый БлокировкаЧтенияЗаписи(); - -КонецПроцедуры - -// Источник данных, соединениями которого работают хранилища. -// -// Возвращаемое значение: -// ИсточникДанных - Источник данных. -// -Функция Источник() Экспорт - Возврат пИсточник; -КонецФункции - -// Возвращает хранилище сущностей, создавая его при первом обращении. -// -// К БД обращения нет, поэтому закрытый источник хранилище выдает: ошибку дает первая операция, -// когда хранилище просит соединение. -// -// Параметры: -// ТипСущности - Тип - Тип сущности, зарегистрированный в модели данных менеджера. -// -// Возвращаемое значение: -// ХранилищеСущностей - Хранилище для переданного типа. -// -Функция Получить(Знач ТипСущности) Экспорт - - пБлокировка.Чтение().Заблокировать(); - Хранилище = пХранилища.Получить(ТипСущности); - пБлокировка.Чтение().Разблокировать(); - - Если Хранилище <> Неопределено Тогда - Возврат Хранилище; - КонецЕсли; - - пБлокировка.Запись().Заблокировать(); - - Попытка - - // Пока ждали запись, хранилище мог создать другой поток. - Хранилище = пХранилища.Получить(ТипСущности); - Если Хранилище = Неопределено Тогда - Хранилище = Новый ХранилищеСущностей(пМодельДанных.Получить(ТипСущности), ЭтотОбъект); - пХранилища.Вставить(ТипСущности, Хранилище); - КонецЕсли; - - Исключение - - пБлокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - пБлокировка.Запись().Разблокировать(); - - Возврат Хранилище; - -КонецФункции - -// Закрывает хранилища менеджера и убирает их из набора. -// -Процедура Закрыть() Экспорт - - пБлокировка.Запись().Заблокировать(); - - Попытка - - Для Каждого КлючИЗначение Из пХранилища Цикл - КлючИЗначение.Значение.Закрыть(); - КонецЦикла; - пХранилища.Очистить(); - - Исключение - - пБлокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - пБлокировка.Запись().Разблокировать(); - -КонецПроцедуры diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index b29dd8e..30a24bc 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -20,18 +20,18 @@ // Сохраняет сущность в БД соединением источника данных. // // Параметры: -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Сохраняемая сущность. // -Процедура Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Сохранить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт ПроверитьЧтоКлассЯвляетсяСущностью(АктивнаяЗапись.ТипСущности(Сущность)); ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); - Пул = Хранилища.Источник().ПулСоединений(); + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -52,7 +52,7 @@ // Ищет сущности соединением источника данных. // // Параметры: -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ОпцииПоиска - Соответствие, ОпцииПоиска - Опции поиска. @@ -60,12 +60,12 @@ // Возвращаемое значение: // Массив - Найденные сущности. // -Функция Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция Получить(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ОпцииПоиска); НайденныеСущности = Новый Массив; - Пул = Хранилища.Источник().ПулСоединений(); + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -74,7 +74,7 @@ НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл - НайденныеСущности.Добавить(СобратьСущность(Коннектор, Хранилища, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); + НайденныеСущности.Добавить(СобратьСущность(Коннектор, Менеджер, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); КонецЦикла; Исключение @@ -91,7 +91,7 @@ // Ищет одну сущность соединением источника данных. // // Параметры: -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ОпцииПоиска - Соответствие, ОпцииПоиска, Произвольный - Опции поиска; любой другой тип - идентификатор. @@ -100,7 +100,7 @@ // Произвольный - Найденная сущность. // Неопределено - Сущность не найдена. // -Функция ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция ПолучитьОдно(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") ИЛИ ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда ПередаваемыеОпцииПоиска = ОпцииПоиска; @@ -109,7 +109,7 @@ ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); КонецЕсли; - НайденныеСущности = Получить(Хранилища, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); + НайденныеСущности = Получить(Менеджер, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); Если НайденныеСущности.Количество() = 0 Тогда Возврат Неопределено; @@ -123,14 +123,14 @@ // Сущность должна иметь заполненный идентификатор. // // Параметры: -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и соседние хранилища. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Удаляемая сущность. // -Процедура Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Удалить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт - Пул = Хранилища.Источник().ПулСоединений(); + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -364,7 +364,7 @@ // // Параметры: // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и хранилища по ссылкам. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. @@ -372,17 +372,17 @@ // Возвращаемое значение: // Произвольный - Заполненная сущность. // -Функция СобратьСущность(Коннектор, Хранилища, ОбъектМодели, ПулСущностей, НайденнаяСтрока) +Функция СобратьСущность(Коннектор, Менеджер, ОбъектМодели, ПулСущностей, НайденнаяСтрока) ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), ОбъектМодели.Идентификатор().ИмяПоля ); - Сущность = ПолучитьСущностьИзПула(Хранилища, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); + Сущность = ПолучитьСущностьИзПула(Менеджер, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); - ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Хранилища, ОбъектМодели, ЗначениеИдентификатора); - ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Хранилища, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Менеджер, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Менеджер, ОбъектМодели, ЗначениеИдентификатора); Возврат Сущность; @@ -457,7 +457,7 @@ // Возвращает экземпляр сущности из пула сущностей, создавая его при первом обращении. // // Параметры: -// Хранилища - ХранилищаСущностей - Хранилища менеджера, которому принадлежит хранилище сущности. +// Менеджер - МенеджерСущностей - Менеджер, которому принадлежит хранилище сущности. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ЗначениеИдентификатора - Произвольный - Идентификатор сущности. @@ -465,14 +465,14 @@ // Возвращаемое значение: // Произвольный - Экземпляр сущности. // -Функция ПолучитьСущностьИзПула(Хранилища, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) +Функция ПолучитьСущностьИзПула(Менеджер, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) Сущность = ПулСущностей.ПолучитьИлиУмолчание(ЗначениеИдентификатора, Неопределено); Если Сущность <> Неопределено Тогда Возврат Сущность; КонецЕсли; - ХранилищеСущностей = Хранилища.Получить(ОбъектМодели.ТипСущности()); + ХранилищеСущностей = Менеджер.ПолучитьХранилищеСущностей(ОбъектМодели.ТипСущности()); Сущность = АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей); // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться @@ -489,11 +489,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. -// Хранилища - ХранилищаСущностей - Хранилища менеджера: дают источник и хранилища по ссылкам. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Хранилища, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Менеджер, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого Колонка Из ОбъектМодели.Колонки() Цикл @@ -503,7 +503,7 @@ ЗначениеКолонки = РазыменоватьСсылку( ЗначениеКолонки, Колонка.ТипСсылки, - Хранилища, + Менеджер, ОбъектМодели, Сущность, ЗначениеИдентификатора @@ -524,7 +524,7 @@ // Параметры: // ЗначениеКолонки - Произвольный - Идентификатор сущности, на которую указывает ссылка. // ТипСсылки - Тип - Тип сущности, на которую указывает ссылка. -// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищется хранилище ссылки. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашивается хранилище ссылки. // ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит колонка. // Сущность - Произвольный - Сущность, которой принадлежит колонка. // ЗначениеИдентификатора - Произвольный - Идентификатор этой сущности. @@ -535,7 +535,7 @@ Функция РазыменоватьСсылку( Знач ЗначениеКолонки, Знач ТипСсылки, - Хранилища, + Менеджер, ОбъектМодели, Сущность, Знач ЗначениеИдентификатора @@ -545,7 +545,7 @@ Возврат Сущность; КонецЕсли; - ХранилищеСущностейСсылки = Хранилища.Получить(ТипСсылки); + ХранилищеСущностейСсылки = Менеджер.ПолучитьХранилищеСущностей(ТипСсылки); Возврат ХранилищеСущностейСсылки.ПолучитьОдно(ЗначениеКолонки); @@ -556,11 +556,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищутся хранилища элементов. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашиваются хранилища элементов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Хранилища, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Менеджер, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл @@ -576,7 +576,7 @@ ЗначениеКолонки = ОбработкаКоллекций.ПреобразоватьКоллекциюСтрокККоллекции(Строки, ПодчиненнаяТаблица); Если ПодчиненнаяТаблица.КаскадноеЧтение И ТипыКолонок.ЭтоСсылочныйТип(ПодчиненнаяТаблица.ТипЭлемента) Тогда - ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Хранилища); + ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Менеджер); КонецЕсли; ОбъектМодели.УстановитьЗначениеПодчиненнойТаблицыВПоле(Сущность, ПодчиненнаяТаблица.ИмяПоля, ЗначениеКолонки); @@ -590,11 +590,11 @@ // Параметры: // Коллекция - Массив - Коллекция идентификаторов, изменяется на месте. // ТипЭлемента - Тип - Тип сущности элементов коллекции. -// Хранилища - ХранилищаСущностей - Хранилища менеджера, среди которых ищется хранилище элементов. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашивается хранилище элементов. // -Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Хранилища) +Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Менеджер) - ХранилищеСущностейСсылки = Хранилища.Получить(ТипЭлемента); + ХранилищеСущностейСсылки = Менеджер.ПолучитьХранилищеСущностей(ТипЭлемента); Для НомерИндекса = 0 По Коллекция.ВГраница() Цикл Коллекция[НомерИндекса] = ХранилищеСущностейСсылки.ПолучитьОдно(Коллекция[НомерИндекса]); diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 4663465..497f9b5 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -1,5 +1,6 @@ #Использовать "../Аннотации" #Использовать "../internal" +#Использовать "../internal/concurrent" #Использовать asserts #Использовать logos @@ -11,10 +12,20 @@ // ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; -// ХранилищаСущностей - Хранилища менеджера по типам сущностей. Принадлежат менеджеру, потому что -// работают его моделью данных. +// Соответствие - Тип сущности: хранилище этого типа. Хранилища принадлежат менеджеру, потому что +// работают его моделью данных: иначе хранилище с моделью закрытого менеджера досталось бы +// следующему. Перем Хранилища; +// БлокировкаЧтенияЗаписи - Защищает набор хранилищ: менеджером пользуются из разных потоков. +// Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, поэтому +// попадание в набор читается под блокировкой чтения: сериализуется только создание. +Перем БлокировкаХранилищ; + +// Булево - Менеджер закрыт. Читается и пишется только при захваченной блокировке записи, поэтому +// хранилище, созданное после начала закрытия, в набор уже не попадет. +Перем Закрыт; + // Лог - Журнал библиотеки. Перем Лог; @@ -45,7 +56,9 @@ Лог.Отладка("Инициализация менеджера сущностей с коннектором %1", Источник.ТипКоннектора()); МодельДанных = Новый МодельДанных; - Хранилища = Новый ХранилищаСущностей(Источник, МодельДанных); + Хранилища = Новый Соответствие(); + БлокировкаХранилищ = Новый БлокировкаЧтенияЗаписи(); + Закрыт = Ложь; КонецПроцедуры @@ -140,7 +153,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(ЭтотОбъект, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -167,7 +180,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(ЭтотОбъект, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -195,7 +208,7 @@ ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(ЭтотОбъект, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -212,7 +225,7 @@ ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(ЭтотОбъект, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -225,7 +238,27 @@ // Процедура Закрыть() Экспорт - Хранилища.Закрыть(); + БлокировкаХранилищ.Запись().Заблокировать(); + + Попытка + + Закрыт = Истина; + + Для Каждого КлючИЗначение Из Хранилища Цикл + КлючИЗначение.Значение.Закрыть(); + КонецЦикла; + Хранилища.Очистить(); + + Исключение + + БлокировкаХранилищ.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + БлокировкаХранилищ.Запись().Разблокировать(); + МодельДанных.Очистить(); КонецПроцедуры @@ -309,6 +342,10 @@ // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // +// Хранилище создается при первом обращении и живет до закрытия менеджера. К БД обращения нет, +// поэтому хранилище выдает и менеджер закрытого источника: ошибку дает первая операция, когда +// хранилище просит соединение. +// // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели // @@ -316,9 +353,50 @@ // ХранилищеСущностей - Хранилище сущностей, привязанное к переданному типу сущности. // Функция ПолучитьХранилищеСущностей(ТипСущности) Экспорт - Возврат Хранилища.Получить(ТипСущности); + + БлокировкаХранилищ.Чтение().Заблокировать(); + Хранилище = Хранилища.Получить(ТипСущности); + БлокировкаХранилищ.Чтение().Разблокировать(); + + Если Хранилище <> Неопределено Тогда + Возврат Хранилище; + КонецЕсли; + + БлокировкаХранилищ.Запись().Заблокировать(); + + Попытка + + ПроверитьЧтоМенеджерНеЗакрыт(); + + // Пока ждали запись, хранилище мог создать другой поток. + Хранилище = Хранилища.Получить(ТипСущности); + Если Хранилище = Неопределено Тогда + Хранилище = Новый ХранилищеСущностей(МодельДанных.Получить(ТипСущности), ЭтотОбъект); + Хранилища.Вставить(ТипСущности, Хранилище); + КонецЕсли; + + Исключение + + БлокировкаХранилищ.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + БлокировкаХранилищ.Запись().Разблокировать(); + + Возврат Хранилище; + КонецФункции +// Проверяет, что менеджер не закрыт. +// +// Вызывается при захваченной блокировке записи: признак закрытия ей и защищен. +// +Процедура ПроверитьЧтоМенеджерНеЗакрыт() + Ожидаем.Что(Закрыт, "Менеджер сущностей закрыт").ЭтоЛожь(); +КонецПроцедуры + // @internal // // Для служебного пользования. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index d2f52df..9fa568e 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -9,23 +9,23 @@ // Карта разделяется всеми потоками исполнения, поэтому синхронизированная. Перем ПулСущностей; -// ХранилищаСущностей - Хранилища менеджера, которому принадлежит это хранилище. Через них -// хранилище получает источник для операции и соседние хранилища при чтении ссылок. -Перем Хранилища; +// МенеджерСущностей - Менеджер, которому принадлежит хранилище. Операции хранилище выполняет не +// само: оно подставляет свой тип сущности и делегирует их менеджеру. +Перем Менеджер; // Создает хранилище, привязанное к типу сущности. // -// Создается набором хранилищ менеджера, напрямую конструировать не следует. +// Создается менеджером сущностей, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ПХранилища - ХранилищаСущностей - Хранилища менеджера, создавшего это хранилище. +// ПМенеджер - МенеджерСущностей - Менеджер, создавший это хранилище. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПХранилища) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПМенеджер) ОбъектМодели = ПОбъектМодели; ПулСущностей = Новый СинхронизированнаяКарта(Новый КартаСоответствие()); - Хранилища = ПХранилища; + Менеджер = ПМенеджер; КонецПроцедуры @@ -48,7 +48,7 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для сохранения в БД. // Процедура Сохранить(Сущность) Экспорт - РаботаСКоннекторами.Сохранить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); + Менеджер.Сохранить(Сущность); КонецПроцедуры // Осуществляет поиск сущностей типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -68,13 +68,7 @@ // экземпляры класса с типом, привязанным к ХранилищуСущностей, с заполненными значениями полей. // Функция Получить(Знач ОпцииПоиска = Неопределено) Экспорт - - Если ОпцииПоиска = Неопределено Тогда - ОпцииПоиска = Новый ОпцииПоиска(); - КонецЕсли; - - Возврат РаботаСКоннекторами.Получить(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); - + Возврат Менеджер.Получить(ОбъектМодели.ТипСущности(), ОпцииПоиска); КонецФункции // Осуществляет поиск сущности типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -95,13 +89,7 @@ // привязанным к ХранилищуСущностей, с заполненными значениями полей. Иначе возвращается "Неопределено". // Функция ПолучитьОдно(Знач ОпцииПоиска = Неопределено) Экспорт - - Если ОпцииПоиска = Неопределено Тогда - ОпцииПоиска = Новый ОпцииПоиска(); - КонецЕсли; - - Возврат РаботаСКоннекторами.ПолучитьОдно(Хранилища, ОбъектМодели, ПулСущностей, ОпцииПоиска); - + Возврат Менеджер.ПолучитьОдно(ОбъектМодели.ТипСущности(), ОпцииПоиска); КонецФункции // Выполняет удаление сущности из базы данных. @@ -114,7 +102,7 @@ // Сущность - Произвольный - Удаляемая сущность // Процедура Удалить(Сущность) Экспорт - РаботаСКоннекторами.Удалить(Хранилища, ОбъектМодели, ПулСущностей, Сущность); + Менеджер.Удалить(Сущность); КонецПроцедуры // Выполняет очистку текущего Хранилища сущностей. @@ -132,19 +120,19 @@ // сам менеджер сущностей. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Хранилища.Источник()); + Менеджер.НачатьТранзакцию(); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Хранилища.Источник()); + Менеджер.ЗафиксироватьТранзакцию(); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Хранилища.Источник()); + Менеджер.ОтменитьТранзакцию(); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -165,7 +153,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннектором(Хранилища.Источник(), Алгоритм, Контекст, Объект); + Менеджер.ВыполнитьСКоннектором(Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -187,7 +175,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Хранилища.Источник(), Алгоритм, Контекст, Объект); + Возврат Менеджер.ВычислитьСКоннектором(Алгоритм, Контекст, Объект); КонецФункции // Возвращает объект модели, связанный с текущим хранилищем сущностей. diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index b1bc633..af68734 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -124,6 +124,40 @@ КонецПроцедуры +// Закрытие менеджера терминально: хранилищ он больше не выдает. Иначе поток, ожидавший создания +// хранилища, создал бы его уже после закрытия, и это хранилище пережило бы менеджер вместе с его +// очищенной моделью. +&Тест +Процедура ЗакрытыйМенеджерХранилищНеВыдает() Экспорт + + ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища после закрытия"); + Менеджер = МенеджерАвторов(Источник); + + Менеджер.Закрыть(); + + ТекстОшибкиХранилища = ""; + Попытка + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Исключение + ТекстОшибкиХранилища = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + ТекстОшибкиЧтения = ""; + Попытка + Менеджер.Получить(Тип("Автор")); + Исключение + ТекстОшибкиЧтения = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибкиХранилища, "Закрытый менеджер хранилище не выдает").Содержит("закрыт"); + Ожидаем.Что(ТекстОшибкиЧтения, "Чтение через закрытый менеджер завершается ошибкой").Содержит("закрыт"); + + Источник.Закрыть(); + +КонецПроцедуры + // Хранилища принадлежат менеджеру, поэтому закрытие менеджера их и закрывает: прочитанные // сущности из пула сущностей уходят. &Тест From 0a8b2e9184bd4a2913ee8e8e5b150668d5244697 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 18:51:39 +0000 Subject: [PATCH 20/55] =?UTF-8?q?=D0=90=D0=BA=D1=82=D0=B8=D0=B2=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D1=82=D1=81=D1=8F=20=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=BF=D0=BE=D1=81=D1=82=D1=80=D0=BE=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit СоздатьИзМенеджера и СоздатьИзХранилища различались только тем, кого зовут Прочитать, Сохранить и Удалить, а после того, как хранилище стало делегировать операции менеджеру, разницы не осталось и в поведении. Остался один Создать, привязывающий запись к хранилищу: тип оно уже знает, поэтому передавать его в методы записи не нужно. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\277\320\270\321\201\321\214.os" | 130 ++++++++---------- ...20\276\321\200\320\260\320\274\320\270.os" | 2 +- ...20\276\321\201\321\202\320\265\320\271.os" | 2 +- ...20\276\321\201\321\202\320\265\320\271.os" | 2 +- ...20\276\321\201\321\202\320\265\320\271.os" | 30 +++- 5 files changed, 84 insertions(+), 82 deletions(-) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" index 7032f54..42ad700 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" @@ -1,76 +1,54 @@ -#Использовать decorator - -Функция ТипСущности(Сущность) Экспорт - ТипСущности = ТипЗнч(Сущность); - Если ТипСущности = Тип("Сценарий") Тогда - ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность); - КонецЕсли; - - Возврат ТипСущности; -КонецФункции - -Функция СоздатьИзМенеджера(ОбъектМодели, МенеджерСущностей) Экспорт - Сущность = Новый(ОбъектМодели.ТипСущности()); - - Декоратор = Новый ПостроительДекоратора(Сущность) - .Импорт(Новый Импорт("decorator")) - - .Поле(Новый Поле("_МенеджерСущностей") - .ЗначениеПоУмолчанию(МенеджерСущностей)) - - .Поле(Новый Поле("_ОбъектМодели") - .ЗначениеПоУмолчанию(ОбъектМодели)) - - .Метод(Новый Метод("Прочитать") - .Публичный() - .ТелоМетода("_ТипСущности = ОбработкаДекоратора.ИсходныйТип(ЭтотОбъект); - |_ДанныеСущности = _МенеджерСущностей.ПолучитьОдно( - | _ТипСущности, - | _ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект) - |); - |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) - - - .Метод(Новый Метод("Сохранить") - .Публичный() - .ТелоМетода("_МенеджерСущностей.Сохранить(ЭтотОбъект);")) - - .Метод(Новый Метод("Удалить") - .Публичный() - .ТелоМетода("_МенеджерСущностей.Удалить(ЭтотОбъект);")) - - .Построить(); - - Возврат Декоратор; -КонецФункции - -Функция СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей) Экспорт - Сущность = Новый(ОбъектМодели.ТипСущности()); - - Декоратор = Новый ПостроительДекоратора(Сущность) - .Импорт(Новый Импорт("decorator")) - - .Поле(Новый Поле("_ХранилищеСущностей") - .ЗначениеПоУмолчанию(ХранилищеСущностей)) - - .Поле(Новый Поле("_ОбъектМодели") - .ЗначениеПоУмолчанию(ОбъектМодели)) - - .Метод(Новый Метод("Прочитать") - .Публичный() - .ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект)); - |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) - - - .Метод(Новый Метод("Сохранить") - .Публичный() - .ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);")) - - .Метод(Новый Метод("Удалить") - .Публичный() - .ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);")) - - .Построить(); - - Возврат Декоратор; -КонецФункции \ No newline at end of file +#Использовать decorator + +Функция ТипСущности(Сущность) Экспорт + ТипСущности = ТипЗнч(Сущность); + Если ТипСущности = Тип("Сценарий") Тогда + ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность); + КонецЕсли; + + Возврат ТипСущности; +КонецФункции + +// Создает экземпляр сущности, расширенный методами паттерна Active Record. +// +// Запись работает через хранилище своего типа, кто бы ее ни создал: хранилище тип уже знает, +// поэтому в методы записи его передавать не нужно, а операцию хранилище все равно делегирует +// своему менеджеру. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель сущности, экземпляр которой создается. +// ХранилищеСущностей - ХранилищеСущностей - Хранилище типа этой сущности. +// +// Возвращаемое значение: +// Произвольный - Экземпляр класса сущности с методами Прочитать, Сохранить и Удалить. +// +Функция Создать(ОбъектМодели, ХранилищеСущностей) Экспорт + Сущность = Новый(ОбъектМодели.ТипСущности()); + + Декоратор = Новый ПостроительДекоратора(Сущность) + .Импорт(Новый Импорт("decorator")) + + .Поле(Новый Поле("_ХранилищеСущностей") + .ЗначениеПоУмолчанию(ХранилищеСущностей)) + + .Поле(Новый Поле("_ОбъектМодели") + .ЗначениеПоУмолчанию(ОбъектМодели)) + + .Метод(Новый Метод("Прочитать") + .Публичный() + .ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект)); + |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) + + + .Метод(Новый Метод("Сохранить") + .Публичный() + .ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);")) + + .Метод(Новый Метод("Удалить") + .Публичный() + .ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);")) + + .Построить(); + + Возврат Декоратор; +КонецФункции diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 30a24bc..0a19bf0 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -473,7 +473,7 @@ КонецЕсли; ХранилищеСущностей = Менеджер.ПолучитьХранилищеСущностей(ОбъектМодели.ТипСущности()); - Сущность = АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей); + Сущность = АктивнаяЗапись.Создать(ОбъектМодели, ХранилищеСущностей); // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться // один, иначе разные потоки получат разные объекты одной строки. Победителем остается тот, diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 497f9b5..a6d7351 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -138,7 +138,7 @@ Функция СоздатьЭлемент(ТипСущности) Экспорт ОбъектМодели = МодельДанных.Получить(ТипСущности); - Возврат АктивнаяЗапись.СоздатьИзМенеджера(ОбъектМодели, ЭтотОбъект); + Возврат АктивнаяЗапись.Создать(ОбъектМодели, ПолучитьХранилищеСущностей(ТипСущности)); КонецФункции // Сохраняет сущность в БД. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 9fa568e..21b2f86 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -36,7 +36,7 @@ // с дополнительными методами паттерна Active Record. // Функция СоздатьЭлемент() Экспорт - Возврат АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ЭтотОбъект); + Возврат АктивнаяЗапись.Создать(ОбъектМодели, ЭтотОбъект); КонецФункции // Сохраняет сущность в БД. diff --git "a/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 21da32f..62136a6 100644 --- "a/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -172,14 +172,38 @@ СохраняемыйАвтор.ВтороеИмя = "Иванов"; СохраняемыйАвтор.Сохранить(); - + Результат = ТестовыеУтилиты.ВыполнитьЗапрос(МенеджерСущностей, "SELECT * FROM Авторы"); Ожидаем.Что(Результат, "В таблице должен был сохраниться новый автор").ИмеетДлину(1); - + Ожидаем .Что(СохраняемыйАвтор.ВнутреннийИдентификатор, "Заполнился и сохранился новый идентификатор сохраняемого автора") .Равно(1); - + +КонецПроцедуры + +// Активная запись строится одним построителем независимо от того, кто ее создал: и элемент +// менеджера, и элемент хранилища работают через хранилище своего типа. +&Тест +Процедура ЭлементМенеджераРаботаетЧерезХранилищеСвоегоТипа() Экспорт + + ХранилищеАвторов = МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("Автор")); + + ЭлементМенеджера = МенеджерСущностей.СоздатьЭлемент(Тип("Автор")); + ЭлементХранилища = ХранилищеАвторов.СоздатьЭлемент(); + + Рефлектор = Новый Рефлектор(); + + Ожидаем.Что( + Рефлектор.ПолучитьСвойство(ЭлементХранилища, "_ХранилищеСущностей") = ХранилищеАвторов, + "Элемент хранилища работает через это хранилище" + ).ЭтоИстина(); + + Ожидаем.Что( + Рефлектор.ПолучитьСвойство(ЭлементМенеджера, "_ХранилищеСущностей") = ХранилищеАвторов, + "Элемент менеджера работает через то же хранилище" + ).ЭтоИстина(); + КонецПроцедуры &Тест From ba056c6132d2f6f6ab323d42e134cd3d8ea61dde Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 19:30:54 +0000 Subject: [PATCH 21/55] =?UTF-8?q?=D0=A2=D0=B2=D0=BE=D1=80=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B6=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=20=D0=B8=D0=B7=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D1=83=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=BA=20=C2=AB=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D1=87=D0=B5=D0=BC-=D1=82=D0=BE=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Менеджер не работает пулом соединений, а берет из него соединения; хранилище не работает моделью данных, а работает с ней. Творительный падеж читался так, будто объект сам является тем, чем работает, - та же ошибка, что была в «хранилище работает источником». Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- README.md | 4 ++-- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.md" | 2 +- ...20\261\320\276\321\202\320\260\320\241\320\221\320\224.md" | 2 +- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.os" | 4 ++-- ...21\202\320\276\321\207\320\275\320\270\320\272\320\260.os" | 2 +- ...\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" | 2 +- ...\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" | 2 +- ...20\260\320\224\320\260\320\275\320\275\321\213\321\205.os" | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index df902b8..4b75acb 100644 --- a/README.md +++ b/README.md @@ -507,7 +507,7 @@ Своей логики работы с БД у хранилища нет: оно подставляет свой тип сущности и делегирует операцию менеджеру, которому принадлежит. Поэтому поведение вызова через хранилище и через менеджер совпадает, в том числе для транзакций и прямого доступа к БД. -Хранилища сущностей и пулы сущностей принадлежат менеджеру сущностей: хранилище работает моделью данных того менеджера, который его создал, и закрывается вместе с ним. Один менеджер на один тип сущности всегда возвращает одно и то же хранилище, а два менеджера, созданные из одного источника данных, возвращают разные хранилища со своими пулами сущностей. Соединения при этом общие: их дает пул источника. +Хранилища сущностей и пулы сущностей принадлежат менеджеру сущностей: хранилище работает с моделью данных того менеджера, который его создал, и закрывается вместе с ним. Один менеджер на один тип сущности всегда возвращает одно и то же хранилище, а два менеджера, созданные из одного источника данных, возвращают разные хранилища со своими пулами сущностей. Соединения при этом общие: их дает пул источника. @@ -530,7 +530,7 @@ > Важно! -Менеджеры и хранилища сущностей одного источника данных собственных коннекторов не держат: они работают общим пулом соединений этого источника. Тип, строка соединения и параметры коннектора задаются при создании ИсточникаДанных. +Менеджеры и хранилища сущностей одного источника данных собственных коннекторов не держат: они берут соединения из общего пула этого источника. Тип, строка соединения и параметры коннектора задаются при создании ИсточникаДанных. diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 5dd57bb..0af0288 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -5,7 +5,7 @@ ```bsl // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного +// Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного // источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его // временем жизни менеджер не управляет: закрывает источник тот, кто его создал. // diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 7109e00..281ab84 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -43,7 +43,7 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" МенеджерСущностей.НачатьТранзакцию(); МенеджерСущностей.Сохранить(Сущность); -// Фоновое задание работает своим соединением и своей транзакцией +// Фоновое задание работает со своим соединением и своей транзакцией МенеджерСущностей.НачатьТранзакцию(); МенеджерСущностей.ЗафиксироватьТранзакцию(); // Записи основного потока не касается diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index a6d7351..e034c8b 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -13,7 +13,7 @@ Перем Источник; // Соответствие - Тип сущности: хранилище этого типа. Хранилища принадлежат менеджеру, потому что -// работают его моделью данных: иначе хранилище с моделью закрытого менеджера досталось бы +// работают с его моделью данных: иначе хранилище с моделью закрытого менеджера досталось бы // следующему. Перем Хранилища; @@ -31,7 +31,7 @@ // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных и работает его пулом соединений: все менеджеры одного +// Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного // источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его // временем жизни менеджер не управляет: закрывает источник тот, кто его создал. // diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index c0318ee..9331d19 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -81,7 +81,7 @@ Ожидаем.Что(Хранилище.Получить(), "Сохраненный автор прочитан хранилищем").ИмеетДлину(1); Ожидаем .Что(Хранилище.ПолучитьОбъектМодели() = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")), - "Хранилище работает объектом модели своего менеджера") + "Хранилище работает с объектом модели своего менеджера") .ЭтоИстина(); Источник.Закрыть(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index d050c33..a9cbb29 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -58,7 +58,7 @@ МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.Инициализировать(); - // Тесты коннектора работают собственным экземпляром: соединения из пула принадлежат пулу. + // Тесты коннектора работают с собственным экземпляром: соединения из пула принадлежат пулу. Коннектор = Новый КоннекторPostgreSQL; Коннектор.Открыть(СтрокаСоединения, Новый Массив); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index eb638e9..2efc042 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -45,7 +45,7 @@ МенеджерСущностей.Инициализировать(); - // Тесты коннектора работают собственным экземпляром: соединения из пула принадлежат пулу. + // Тесты коннектора работают с собственным экземпляром: соединения из пула принадлежат пулу. Коннектор = Новый КоннекторSQLite; Коннектор.Открыть(СтрокаСоединения, Новый Массив); diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index af68734..18e7bc9 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -185,7 +185,7 @@ КонецПроцедуры -// Хранилище работает моделью данных своего менеджера, поэтому и принадлежит менеджеру: у двух +// Хранилище работает с моделью данных своего менеджера, поэтому и принадлежит менеджеру: у двух // менеджеров одного источника хранилища разные, как и пулы сущностей в них. &Тест Процедура ХранилищаПринадлежатМенеджеруАНеИсточнику() Экспорт From 9c1c8796aa5824a7093cabd3db0787ffe59e63f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 20:08:04 +0000 Subject: [PATCH 22/55] =?UTF-8?q?Bsldoc=20=D0=BE=D0=BF=D0=B8=D1=81=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0,=20=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D1=81=D0=BE=D1=81=D0=B5=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Из описаний ушли утверждения о том, чего класс не делает и не знает, и подробности устройства соседей: источник больше не рассказывает про модель данных и хранилища менеджера, менеджер - про владение соединениями и про общий пул источника, хранилище - про то, где живут коннекторы. Осталось то, что нужно вызывающему: что метод делает, что возвращает и когда завершается ошибкой. Заодно в документации хранилища обновлены описания транзакций: там оставались формулировки времени, когда хранилище посылало запросы коннектору напрямую. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.md" | 7 +++---- ...20\276\321\201\321\202\320\265\320\271.md" | 19 ++++++++----------- ...20\276\321\201\321\202\320\265\320\271.md" | 11 +++++++---- ...20\260\320\275\320\275\321\213\321\205.os" | 7 +++---- ...20\276\321\201\321\202\320\265\320\271.os" | 19 ++++++++----------- ...20\276\321\201\321\202\320\265\320\271.os" | 11 +++++------ 6 files changed, 34 insertions(+), 40 deletions(-) diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index 2fccc4e..f6c2574 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -7,10 +7,9 @@ пул открывает при первом обращении. Закрывает пул сам источник: методом `Закрыть()` или по завершению потока, в котором источник создан, если автозакрытие не выключено, см. `УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник соединений больше не выдает, -поэтому любая операция через него завершается ошибкой. О сущностях источник не знает: модель -данных и хранилища принадлежат менеджеру сущностей, а закрытие источника их не касается. -Тип коннектора и размер пула проверяются при создании: тип без интерфейса -`АбстрактныйКоннектор` и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. +поэтому любая операция через него завершается ошибкой. Тип коннектора и размер пула +проверяются при создании: тип без интерфейса `АбстрактныйКоннектор` и размер, не являющийся +неотрицательным числом, отвергаются с ошибкой. ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 0af0288..06a4488 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -6,8 +6,8 @@ // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного -// источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его -// временем жизни менеджер не управляет: закрывает источник тот, кто его создал. +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его +// временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. @@ -134,9 +134,8 @@ // Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер // к работе непригоден. // -// Источник данных не закрывается: его соединения принадлежат тому, кто источник создал, -// см. ИсточникДанных.Закрыть(). Хранилища других менеджеров того же источника не затрагиваются: -// каждый менеджер владеет своими. +// Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает +// ИсточникДанных.Закрыть(). // Процедура Закрыть() ``` @@ -210,9 +209,7 @@ ```bsl // Источник данных, из которого создан менеджер. // -// Все обращения менеджера к БД идут соединениями этого источника. Хранилища сущностей и все -// менеджеры одного источника делят его пул: предел одновременно живущих соединений общий, -// а транзакция потока видна из каждого из них. +// Все обращения менеджера к БД идут через соединения этого источника. // // Возвращаемое значение: // ИсточникДанных - Источник данных менеджера. @@ -225,9 +222,9 @@ ```bsl // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // -// Хранилище создается при первом обращении и живет до закрытия менеджера. К БД обращения нет, -// поэтому хранилище выдает и менеджер закрытого источника: ошибку дает первая операция, когда -// хранилище просит соединение. +// Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности +// менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается +// ошибкой. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 007833d..21a1c30 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -93,7 +93,7 @@ ```bsl // Выполняет очистку текущего Хранилища сущностей. // -// Коннекторов хранилище не держит: они живут в пуле источника данных, и закрывает их источник. +// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. // Процедура Закрыть() Экспорт ``` @@ -101,7 +101,10 @@ ### НачатьТранзакцию ```bsl -// Посылает коннектору запрос на начало транзакции. +// Начинает транзакцию для текущего потока исполнения. +// +// Транзакция принадлежит потоку исполнения: в нее попадают все его операции, в том числе через +// другие хранилища и через сам менеджер сущностей. // Процедура НачатьТранзакцию() Экспорт ``` @@ -109,7 +112,7 @@ ### ЗафиксироватьТранзакцию ```bsl -// Посылает коннектору запрос на фиксацию транзакции. +// Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт ``` @@ -117,7 +120,7 @@ ### ОтменитьТранзакцию ```bsl -// Посылает коннектору запрос на отмену транзакции. +// Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт ``` diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 2876b01..f7068a5 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -44,10 +44,9 @@ // пул открывает при первом обращении. Закрывает пул сам источник: методом Закрыть() или по // завершению потока, в котором источник создан, если автозакрытие не выключено, см. // УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник соединений больше не выдает, -// поэтому любая операция через него завершается ошибкой. О сущностях источник не знает: модель -// данных и хранилища принадлежат менеджеру сущностей, а закрытие источника их не касается. -// Тип коннектора и размер пула проверяются при создании: тип без интерфейса -// АбстрактныйКоннектор и размер, не являющийся неотрицательным числом, отвергаются с ошибкой. +// поэтому любая операция через него завершается ошибкой. Тип коннектора и размер пула +// проверяются при создании: тип без интерфейса АбстрактныйКоннектор и размер, не являющийся +// неотрицательным числом, отвергаются с ошибкой. // // Параметры: // Имя - Строка - Имя источника данных. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index e034c8b..bf4dcde 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -32,8 +32,8 @@ // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного -// источника делят соединения и транзакции потоков. Параметры соединения задает источник, и его -// временем жизни менеджер не управляет: закрывает источник тот, кто его создал. +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его +// временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. @@ -232,9 +232,8 @@ // Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер // к работе непригоден. // -// Источник данных не закрывается: его соединения принадлежат тому, кто источник создал, -// см. ИсточникДанных.Закрыть(). Хранилища других менеджеров того же источника не затрагиваются: -// каждый менеджер владеет своими. +// Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает +// ИсточникДанных.Закрыть(). // Процедура Закрыть() Экспорт @@ -329,9 +328,7 @@ // Источник данных, из которого создан менеджер. // -// Все обращения менеджера к БД идут соединениями этого источника. Хранилища сущностей и все -// менеджеры одного источника делят его пул: предел одновременно живущих соединений общий, -// а транзакция потока видна из каждого из них. +// Все обращения менеджера к БД идут через соединения этого источника. // // Возвращаемое значение: // ИсточникДанных - Источник данных менеджера. @@ -342,9 +339,9 @@ // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // -// Хранилище создается при первом обращении и живет до закрытия менеджера. К БД обращения нет, -// поэтому хранилище выдает и менеджер закрытого источника: ошибку дает первая операция, когда -// хранилище просит соединение. +// Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности +// менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается +// ошибкой. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 21b2f86..7a0053c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -9,8 +9,8 @@ // Карта разделяется всеми потоками исполнения, поэтому синхронизированная. Перем ПулСущностей; -// МенеджерСущностей - Менеджер, которому принадлежит хранилище. Операции хранилище выполняет не -// само: оно подставляет свой тип сущности и делегирует их менеджеру. +// МенеджерСущностей - Менеджер, которому принадлежит хранилище: хранилище подставляет в операции +// свой тип сущности и делегирует их ему. Перем Менеджер; // Создает хранилище, привязанное к типу сущности. @@ -107,7 +107,7 @@ // Выполняет очистку текущего Хранилища сущностей. // -// Коннекторов хранилище не держит: они живут в пуле источника данных, и закрывает их источник. +// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. // Процедура Закрыть() Экспорт ПулСущностей.Очистить(); @@ -115,9 +115,8 @@ // Начинает транзакцию для текущего потока исполнения. // -// Транзакция принадлежит потоку, а не хранилищу: она открывается на коннекторе из пула и -// распространяется на все операции этого потока, в том числе через другие хранилища и через -// сам менеджер сущностей. +// Транзакция принадлежит потоку исполнения: в нее попадают все его операции, в том числе через +// другие хранилища и через сам менеджер сущностей. // Процедура НачатьТранзакцию() Экспорт Менеджер.НачатьТранзакцию(); From e9bdc76eb08f95237e185212b5c6ab99a7be2386 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:31:34 +0000 Subject: [PATCH 23/55] =?UTF-8?q?=D0=94=D0=B5=D0=BA=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80=20=D0=BF=D0=BE=D0=B4=D0=BD=D1=8F=D1=82=20?= =?UTF-8?q?=D0=B4=D0=BE=203.0.0,=20=D0=B2=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20=D1=80=D0=B0=D0=B7=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20moskito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moskito 1.0.0 требует decorator 3.0.0, а entity стоял на 2.0.8. Обновление проверено прогоном: АктивнаяЗапись строится на декораторе напрямую, и все тесты на третьей версии зеленые. Моки нужны для тестов пула: поддельные потоки исполнения позволяют проверять привязку "поток - соединение" в одном потоке, без фоновых заданий и пауз. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- packagedef | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packagedef b/packagedef index 61d3a33..f59fece 100644 --- a/packagedef +++ b/packagedef @@ -23,9 +23,10 @@ .ЗависитОт("fs", "1.2.0") .ЗависитОт("lambdas", "0.3.4") .ЗависитОт("logos", "1.7.1") - .ЗависитОт("decorator", "2.0.8") + .ЗависитОт("decorator", "3.0.0") .ЗависитОт("reflector", "0.7.1") .ЗависитОт("sql", "1.3.3") .ЗависитОт("strings", "0.5.0") + .РазработкаЗависитОт("moskito", "1.0.0") .РазработкаЗависитОт("oneunit", "0.4.1") ; From 33c05bf635fa026aa3fec7123c07433c259dfe00 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:31:46 +0000 Subject: [PATCH 24/55] =?UTF-8?q?=D0=9D=D0=B5=D1=83=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=83=D0=BB=D0=B0=20=D0=BD=D0=B5=20=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D1=8B=D0=B5=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Свободные и ожидающие соединения пул разбирал разрушительно - соединение выбывало из очереди до попытки закрыть. Соединения потоков обходились иначе: соответствие очищалось только после цикла. Если закрытие спотыкалось на середине, уже закрытые соединения оставались закрепленными за своими потоками, а пул закрытым не считался - и поток получал из Занять() закрытое соединение, падая на уровне драйвера. Теперь запись выбывает из соответствия сразу после того, как соединение закрыто, и освобождает свое место в пуле. Соединение, на котором закрытие упало, остается за потоком: его состояние неизвестно, и повтор пробует снова. Тест однопоточный: ПулСоединенийСБД получает поставщика текущего потока параметром конструктора, и на его место в тесте становится мок. Это шов ради проверяемости - наружу пул внутренний, ИсточникДанных седьмой параметр не передает. Разная судьба закрытия у соединений одного пула задается общим планом закрытий в параметрах тестового коннектора. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\275\320\265\320\275\320\270\321\217.os" | 15 +++++ ...20\270\320\271\320\241\320\221\320\224.os" | 50 +++++++++++--- ...21\200\321\213\321\202\320\270\320\265.os" | 20 +++++- ...20\272\321\202\320\276\321\200\320\260.os" | 37 +++++++++++ ...20\270\320\271\320\241\320\221\320\224.os" | 65 ++++++++++++++++++- 5 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 "src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" create mode 100644 "tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" new file mode 100644 index 0000000..f7906bb --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,15 @@ +// Поставщик текущего потока исполнения. +// +// Отдельный класс нужен, чтобы поток приходил к владельцу извне, а не из глобального контекста: +// тогда в тестах на его место становится поддельный поставщик и привязка "поток - ресурс" +// проверяется в одном потоке, без фоновых заданий и пауз. +// + +// Поток исполнения, в котором выполняется текущий код. +// +// Возвращаемое значение: +// ПотокИсполнения - Текущий поток исполнения. +// +Функция Текущий() Экспорт + Возврат ТекущийПоток(); +КонецФункции diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 85bd28d..d6bc216 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -63,6 +63,11 @@ // Перем ПараметрыКоннектора; +// ПотокиИсполнения - Поставщик текущего потока исполнения. Пул спрашивает поток у него, а не у +// глобального контекста: в тестах на его место становится поддельный поставщик. +// +Перем Потоки; + // Пул соединений с базой данных. // // Через пул идут все обращения к БД. Соединение выдается потоку исполнения на время операции @@ -98,6 +103,7 @@ // ППараметрыКоннектора - Массив - Дополнительные параметры коннектора. // ППределОжидания - Число - Предел ожидания соединения в очереди, в миллисекундах. // ППаузаОжидания - Число - Пауза между проверками своей заявки ожидающим, в миллисекундах. +// ППотоки - ПотокиИсполнения - Поставщик текущего потока исполнения. По умолчанию настоящий. // Процедура ПриСозданииОбъекта( Знач ПРазмерПула, @@ -105,7 +111,8 @@ Знач ПСтрокаСоединения, Знач ППараметрыКоннектора, Знач ППределОжидания = 30000, - Знач ППаузаОжидания = 10 + Знач ППаузаОжидания = 10, + Знач ППотоки = Неопределено ) // Отрицательный размер не отвергает ни один захват явно: предел просто никогда не позволяет @@ -121,6 +128,7 @@ ТипКоннектора = ПТипКоннектора; СтрокаСоединения = ПСтрокаСоединения; ПараметрыКоннектора = ППараметрыКоннектора; + Потоки = ?(ППотоки = Неопределено, Новый ПотокиИсполнения(), ППотоки); // Возвращенное позже считается меньшим, поэтому оказывается в голове очереди. Свободные = Новый ПриоритетнаяОчередь( @@ -159,7 +167,7 @@ // BSLLS:UnusedLocalMethod-off Процедура ПриЗавершенииПотокаИсполнения() - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Запись().Заблокировать(); @@ -188,7 +196,7 @@ // Функция Занять() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); // ПотокИсполнения может не дойти до Освободить(): оборваться, забыть освободить или просто // закончиться. Подписка снимает с пула обязанность выяснять это опросом - о завершении @@ -228,7 +236,7 @@ // Процедура Освободить() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Запись().Заблокировать(); @@ -254,7 +262,7 @@ // Функция СоединениеТекущегоПотокаИсполнения() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Чтение().Заблокировать(); Результат = СоединенияПотоковИсполнения.Получить(ПотокИсполнения); @@ -391,11 +399,16 @@ Попытка - Для Каждого КлючИЗначение Из СоединенияПотоковИсполнения Цикл - КлючИЗначение.Значение.Закрыть(); + // Соединение выбывает из соответствия сразу после того, как закрыто, - так же, как выбывает + // из очереди свободное. Если закрытие споткнется на следующем, уже закрытые ни одному + // потоку не достанутся, а повтор закроет остаток. Соединение, на котором закрытие упало, + // остается за потоком: его состояние неизвестно, и повтор должен попробовать снова. + Для Каждого ПотокИсполнения Из ПотокиСЗанятымиСоединениями() Цикл + СоединенияПотоковИсполнения.Получить(ПотокИсполнения).Закрыть(); + СоединенияПотоковИсполнения.Удалить(ПотокИсполнения); + ГлубиныЗахвата.Удалить(ПотокИсполнения); + СозданоСоединений = СозданоСоединений - 1; КонецЦикла; - СоединенияПотоковИсполнения.Очистить(); - ГлубиныЗахвата.Очистить(); Пока Свободные.Количество() > 0 Цикл Свободные.Взять().Получить().Закрыть(); @@ -431,6 +444,25 @@ КонецПроцедуры +// Потоки исполнения, за которыми сейчас закреплены соединения, отдельным массивом. +// +// По самому соответствию идти нельзя: записи удаляются прямо в цикле закрытия. +// +// Возвращаемое значение: +// Массив - Потоки исполнения с занятыми соединениями. +// +Функция ПотокиСЗанятымиСоединениями() + + Результат = Новый Массив(); + + Для Каждого КлючИЗначение Из СоединенияПотоковИсполнения Цикл + Результат.Добавить(КлючИЗначение.Ключ); + КонецЦикла; + + Возврат Результат; + +КонецФункции + // Одна попытка занять соединение. Вызывается при захваченной блокировке записи. // // Параметры: diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index 9f7de3c..c3137de 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -9,6 +9,8 @@ // [1] - Булево - Истина, если открытие должно завершиться ошибкой. // [2] - Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. // [3] - Булево - Истина, если закрытие должно завершиться ошибкой. +// [4] - ПланЗакрытийКоннектора - Необязательный план: с какого по счету закрытия в пуле +// начинать падать. Задан - решает он, а не признак [3]. // // BSLLS:MagicNumber-off @@ -18,11 +20,14 @@ Перем ПадатьПриЗавершенииТранзакции; // Булево - Признак того, что закрытие должно падать. Перем ПадатьПриЗакрытии; +// ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Неопределено, если не задан. +Перем ПланЗакрытий; Процедура ПриСозданииОбъекта() Открыт = Ложь; ПадатьПриЗавершенииТранзакции = Ложь; ПадатьПриЗакрытии = Ложь; + ПланЗакрытий = Неопределено; КонецПроцедуры // Открывает соединение так, как задано параметрами коннектора. @@ -44,6 +49,10 @@ ПадатьПриЗавершенииТранзакции = ПараметрыКоннектора.Количество() > 2 И ПараметрыКоннектора[2]; ПадатьПриЗакрытии = ПараметрыКоннектора.Количество() > 3 И ПараметрыКоннектора[3]; + Если ПараметрыКоннектора.Количество() > 4 Тогда + ПланЗакрытий = ПараметрыКоннектора[4]; + КонецЕсли; + Открыт = Истина; КонецПроцедуры @@ -77,10 +86,19 @@ // Закрывает соединение, падая, если так задано параметрами. // Процедура Закрыть() Экспорт - Если ПадатьПриЗакрытии Тогда + + Если ПланЗакрытий <> Неопределено Тогда + ПадаемСейчас = ПланЗакрытий.ЗакрытиеПадает(); + Иначе + ПадаемСейчас = ПадатьПриЗакрытии; + КонецЕсли; + + Если ПадаемСейчас Тогда ВызватьИсключение "Закрытие соединения не удалось"; КонецЕсли; + Открыт = Ложь; + КонецПроцедуры // Возвращает состояние соединения. diff --git "a/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" new file mode 100644 index 0000000..5924d3f --- /dev/null +++ "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -0,0 +1,37 @@ +// План закрытий для КоннекторУправляемоеОткрытие: с какого по счету закрытия начинать падать. +// +// Один экземпляр разделяется всеми коннекторами пула: он приходит к ним параметрами коннектора, +// а параметры пул передает каждому создаваемому коннектору как есть. Так в одном пуле получаются +// соединения с разной судьбой закрытия, чего одним признаком "падать при закрытии" не добиться. +// + +// Число - Номер закрытия, начиная с которого закрытие завершается ошибкой. +Перем ПадатьНачинаяС; +// Число - Сколько закрытий уже выполнено по этому плану. +Перем ВыполненоЗакрытий; + +// План закрытий. +// +// Параметры: +// ПНомерПервогоПадения - Число - Номер закрытия, с которого начинаются ошибки. Первое закрытие +// имеет номер 1. +// +Процедура ПриСозданииОбъекта(Знач ПНомерПервогоПадения) + + ПадатьНачинаяС = ПНомерПервогоПадения; + ВыполненоЗакрытий = 0; + +КонецПроцедуры + +// Регистрирует очередное закрытие и отвечает, должно ли оно завершиться ошибкой. +// +// Возвращаемое значение: +// Булево - Истина, если это закрытие должно упасть. +// +Функция ЗакрытиеПадает() Экспорт + + ВыполненоЗакрытий = ВыполненоЗакрытий + 1; + + Возврат ВыполненоЗакрытий >= ПадатьНачинаяС; + +КонецФункции diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index ff9c2a5..7ef5ff9 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -9,6 +9,8 @@ #Использовать ".." #Использовать "utils" +#Использовать moskito + // Пул, который тесты создают заново перед каждым тестом. Перем Пул; // Пул с пределом в одно соединение для проверки ожидания. Виден фоновому заданию. @@ -36,6 +38,10 @@ ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), "КоннекторУправляемоеОткрытие" ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "ПланЗакрытийКоннектора.os"), + "ПланЗакрытийКоннектора" + ); ОпределитьТестовуюБазу(); @@ -765,6 +771,60 @@ КонецПроцедуры +// Закрытие, упавшее на одном соединении, не должно оставить за потоками уже закрытые соединения: +// иначе поток получит из пула закрытое соединение и упадет на уровне драйвера, а не на понятной +// ошибке о закрытом пуле. +// +// Потоки исполнения подставлены поддельные, поэтому тест однопоточный: проверяется привязка +// "поток - соединение", а не конкуренция потоков. +// +&Тест +Процедура НеудачноеЗакрытиеНеОставляетЗакрытыеСоединенияЗаПотоками() Экспорт + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ПервыйПоток = Мок.Получить(Тип("ПотокИсполнения")); + ВторойПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулПадающий = Новый ПулСоединенийСБД( + 2, + Тип("КоннекторУправляемоеОткрытие"), + "", + ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2)), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулПадающий.Занять(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + ПулПадающий.Занять(); + + // Второе по счету закрытие падает: пул закрытым не считается, закрытие можно повторить. + ОшибкаЗакрытия = ""; + Попытка + ПулПадающий.Закрыть(); + Исключение + ОшибкаЗакрытия = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем + .Что(ОшибкаЗакрытия, "Закрытие споткнулось на втором соединении") + .Содержит("Закрытие соединения не удалось"); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + Ожидаем + .Что(ПулПадающий.Занять().ПолучитьКоннектор().Открыт(), "Первому потоку досталось живое соединение") + .ЭтоИстина(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + Ожидаем + .Что(ПулПадающий.Занять().ПолучитьКоннектор().Открыт(), "Второму потоку досталось живое соединение") + .ЭтоИстина(); + +КонецПроцедуры + // Закрытие пула терминально. Иначе ожидающий, проснувшийся после закрытия, молча открыл бы // себе новое соединение, которое пережило бы закрытие пула. // @@ -906,6 +966,7 @@ // ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. // ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. // ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. +// ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. // // Возвращаемое значение: // Массив - Параметры коннектора. @@ -914,7 +975,8 @@ Знач ПаузаОткрытия, Знач ПадатьПриОткрытии, Знач ПадатьПриЗавершенииТранзакции = Ложь, - Знач ПадатьПриЗакрытии = Ложь + Знач ПадатьПриЗакрытии = Ложь, + Знач ПланЗакрытий = Неопределено ) Параметры = Новый Массив; @@ -922,6 +984,7 @@ Параметры.Добавить(ПадатьПриОткрытии); Параметры.Добавить(ПадатьПриЗавершенииТранзакции); Параметры.Добавить(ПадатьПриЗакрытии); + Параметры.Добавить(ПланЗакрытий); Возврат Параметры; From 973b18ad6088f8da4f67a3e151fc8b75c878aff5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:41:09 +0000 Subject: [PATCH 25/55] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D0=B2=D0=BE?= =?UTF-8?q?=D0=B7=D0=B2=D1=80=D0=B0=D1=82=D0=B0=20=D1=81=D0=BE=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=B1=D1=80=D1=8B=D0=B2=D0=B0=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Фоновое задание было нужно не ради параллельности, а чтобы поток завершился и пул вернул брошенное соединение. Теперь поток поддельный, а событие завершения поднимается явно через ВызватьСобытие - проверяется реакция пула, а не умение движка обрывать задания. Заодно ушла зависимость от расторопности раннера: ждать завершения задания больше не нужно. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 67 ++++++++++++------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 7ef5ff9..d9f01ea 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -393,22 +393,36 @@ КонецПроцедуры -// Задание оборвалось, не освободив соединение. Соединение возвращается в пул сразу по завершении +// Поток оборвался, не освободив соединение. Соединение возвращается в пул сразу по завершении // потока - иначе оно было бы потеряно навсегда. // +// Поток исполнения подставлен поддельный, а событие завершения поднимается явно: здесь проверяется +// реакция пула на завершение потока, а не умение движка обрывать фоновые задания. +// &Тест -Процедура СоединениеОборвавшегосяЗаданияВозвращаетсяВПул() Экспорт +Процедура СоединениеОборвавшегосяПотокаВозвращаетсяВПул() Экспорт - ПулСПределом = Новый ПулСоединенийСБД(1, Тип("КоннекторInMemory"), "", Новый Массив); + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулОборванного = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторInMemory"), + "", + Новый Массив, + 1000, + 10, + ПоддельныеПотоки + ); Попытка - ПроверитьВозвратПослеОбрыва(); + ПроверитьВозвратПослеОбрыва(ПулОборванного, ПоддельныеПотоки, ОборвавшийсяПоток); Исключение - ПулСПределом.Закрыть(); + ПулОборванного.Закрыть(); ВызватьИсключение; КонецПопытки; - ПулСПределом.Закрыть(); + ПулОборванного.Закрыть(); КонецПроцедуры @@ -430,38 +444,39 @@ КонецПроцедуры -// Проверяет, что соединение оборвавшегося задания достается следующему потоку. +// Проверяет, что соединение оборвавшегося потока достается следующему. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул с пределом в одно соединение. +// Потоки - ПотокиИсполнения - Поддельный поставщик текущего потока. +// ОборвавшийсяПоток - ПотокИсполнения - Поток, который займет соединение и завершится. // -Процедура ПроверитьВозвратПослеОбрыва() +Процедура ПроверитьВозвратПослеОбрыва(Пул, Потоки, ОборвавшийсяПоток) - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьИОборватьсяВФоне", Новый Массив, Истина); - ДождатьсяЗадания(Задание); + Потоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + Пул.Занять(); + + Ожидаем.Что(Пул.КоличествоЗанятых(), "Соединение закреплено за потоком").Равно(1); + + // Поток завершился, не освободив соединение: движок поднял бы это событие сам. + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); Ожидаем - .Что(Задание.ИнформацияОбОшибке <> Неопределено, "Задание оборвалось, не освободив соединение") - .ЭтоИстина(); - Ожидаем - .Что(ПулСПределом.КоличествоЗанятых(), "За оборвавшимся потоком соединение не осталось") + .Что(Пул.КоличествоЗанятых(), "За оборвавшимся потоком соединение не осталось") .Равно(0); Ожидаем - .Что(ПулСПределом.КоличествоСвободных(), "Соединение вернулось в пул сразу по завершении потока") + .Что(Пул.КоличествоСвободных(), "Соединение вернулось в пул сразу по завершении потока") .Равно(1); // Предел в одно соединение исчерпан: следующий поток получает брошенное без ожидания. - ПулСПределом.Занять(); - - Ожидаем.Что(ПулСПределом.КоличествоЗанятых(), "Соединение выдано текущему потоку").Равно(1); - Ожидаем.Что(ПулСПределом.КоличествоСвободных(), "Свободных не осталось").Равно(0); + Потоки.Когда().Текущий().ТогдаВозвращает(Мок.Получить(Тип("ПотокИсполнения"))); + Пул.Занять(); - ПулСПределом.Освободить(); + Ожидаем.Что(Пул.КоличествоЗанятых(), "Соединение выдано следующему потоку").Равно(1); + Ожидаем.Что(Пул.КоличествоСвободных(), "Свободных не осталось").Равно(0); -КонецПроцедуры + Пул.Освободить(); -// Занимает соединение и обрывается, не освободив его. Запускается в фоновом задании. -// -Процедура ЗанятьИОборватьсяВФоне() Экспорт - ПулСПределом.Занять(); - ВызватьИсключение "Обрыв задания с занятым соединением"; КонецПроцедуры // При нулевом размере освобожденное соединение закрывается, а не встает в очередь: следующий From 8f48878a048ab6800fbb4524229c12b1df52121d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:53:24 +0000 Subject: [PATCH 26/55] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D1=84=D0=BE?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тест писался под схему, в которой пул опрашивал ФоновыеЗадания, чтобы понять, жив ли владелец соединения: после Очистить() живое задание из списка пропадало, и пул отбирал у него соединение. Схемы этой больше нет - слова ФоновыеЗадания в пуле не осталось, соединение возвращается только по событию завершения потока, и опрашивать нечего. Проверять за движком его собственную гарантию "Очистить() не завершает живой поток" - не наша задача, а стоил тест 2.6 секунды прогона и живого фонового задания. Геттер ЕстьСоединениеТекущегоПотокаИсполнения остается покрыт тестами выдачи и возврата соединения. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 64 ------------------- 1 file changed, 64 deletions(-) diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index d9f01ea..59f5d97 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -509,70 +509,6 @@ КонецПроцедуры -// Живой поток не должен терять соединение из-за очистки списка фоновых заданий: прежняя -// схема опроса переставала видеть живое задание и отбирала у него соединение. -// -&Тест -Процедура ОчисткаСпискаЗаданийНеОтбираетСоединениеУЖивогоПотокаИсполнения() Экспорт - - ПулСПределом = Новый ПулСоединенийСБД(1, Тип("КоннекторInMemory"), "", Новый Массив, 1000, 10); - - Попытка - - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ДержатьСоединениеИПроверить", Новый Массив, Истина); - Приостановить(700); - - Ожидаем - .Что(ПулСПределом.КоличествоЗанятых(), "Задание заняло единственное соединение") - .Равно(1); - - ФоновыеЗадания.Очистить(); - - // Предел исчерпан. Основной поток просит соединение и получает отказ по таймауту - ОтказПолучен = Ложь; - Попытка - ПулСПределом.Занять(); - ПулСПределом.Освободить(); - Исключение - // Отказ ожидаем: единственное соединение занято живым заданием - ОтказПолучен = Истина; - КонецПопытки; - - Ожидаем - .Что(ОтказПолучен, "Соединение живого задания не отдано основному потоку") - .ЭтоИстина(); - - Задание.ОжидатьЗавершения(); - - Ожидаем - .Что(Задание.Результат, "За живым потоком осталось его соединение") - .ЭтоИстина(); - - Исключение - ПулСПределом.Закрыть(); - ВызватьИсключение; - КонецПопытки; - - ПулСПределом.Закрыть(); - -КонецПроцедуры - -// Занимает соединение, удерживает его и сообщает, осталось ли оно закрепленным. -// -// Возвращаемое значение: -// Булево - Истина, если соединение осталось за потоком. -// -Функция ДержатьСоединениеИПроверить() Экспорт - - ПулСПределом.Занять(); - Приостановить(2500); - Результат = ПулСПределом.ЕстьСоединениеТекущегоПотокаИсполнения(); - ПулСПределом.Освободить(); - - Возврат Результат; - -КонецФункции - // Открытие соединения - сетевой вызов, и держать на нем блокировку пула нельзя: иначе // недоступная база морозит всех, включая тех, кто хочет вернуть исправное соединение. // From 84094e6d6111a55c54a81ba9e676412e07442dcf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:19:20 +0000 Subject: [PATCH 27/55] =?UTF-8?q?=D0=98=D0=B7=20=D0=BF=D1=83=D0=BB=D0=B0?= =?UTF-8?q?=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20=D0=B3=D0=B5=D1=82?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D1=8B=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В рабочем коде их не звал никто: пул берут только чтобы занять и освободить соединение - менеджер при инициализации и РаботаСКоннекторами в операциях. Сами значения пулу нужны, чтобы открывать соединения и держать предел, но отдавать их наружу некому. Владелец описания источника - ИсточникДанных, у него эти геттеры и есть, и они переживают закрытие. Тесты, которые через них подглядывали в устройство, заменены проверками наблюдаемого поведения: - вместо сверки параметров пула с параметрами источника тест создания менеджера проверяет, коннектором какого типа менеджер ходит в базу; - проверка "параметры дошли до пула" заменена проверкой того, ради чего она писалась: правка массива после создания источника его параметров не меняет. Что параметры доходят до самого коннектора, видно по тестам пула: КоннекторУправляемоеОткрытие ими и управляется. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 36 ------------------- ...21\202\320\276\321\200\320\276\320\274.os" | 30 ---------------- ...20\260\320\275\320\275\321\213\321\205.os" | 34 +++++++++++++----- 3 files changed, 25 insertions(+), 75 deletions(-) diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index d6bc216..71d2d36 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -351,42 +351,6 @@ КонецФункции -// Возвращает тип коннектора, которым пул открывает соединения. -// -// Возвращаемое значение: -// Тип - Тип класса, реализующего интерфейс коннектора. -// -Функция ТипКоннектора() Экспорт - Возврат ТипКоннектора; -КонецФункции - -// Возвращает строку соединения, с которой пул открывает соединения. -// -// Возвращаемое значение: -// Строка - Строка соединения с БД. -// -Функция СтрокаСоединения() Экспорт - Возврат СтрокаСоединения; -КонецФункции - -// Возвращает дополнительные параметры коннекторов пула. -// -// Возвращаемое значение: -// Массив - Параметры коннектора. -// -Функция ПараметрыКоннектора() Экспорт - Возврат ПараметрыКоннектора; -КонецФункции - -// Возвращает размер пула. -// -// Возвращаемое значение: -// Число - Предел одновременно живущих соединений; 0 - без предела. -// -Функция РазмерПула() Экспорт - Возврат РазмерПула; -КонецФункции - // Закрывает все соединения пула. // // Закрытие терминально: после него пул соединений не выдает, и `Занять()` завершается ошибкой. diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index 2ae7dc7..ae26b1a 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -267,36 +267,6 @@ КонецПроцедуры -// Параметры коннектора, переданные в конструктор менеджера, доходят до пула. -// -&Тест -Процедура ПараметрыКоннектораДоходятДоПула() Экспорт - - ПараметрыКоннектора = Новый Массив; - ПараметрыКоннектора.Добавить("ПроизвольныйПараметр"); - - ИсточникПараметров = Новый ИсточникДанных( - "Проверка параметров", - Тип("КоннекторInMemory"), - "проверка параметров", - ПараметрыКоннектора - ); - Менеджер = Новый МенеджерСущностей(ИсточникПараметров); - // Массив вызывающего кода источнику не принадлежит: правка после создания до пула не доходит. - ПараметрыКоннектора.Добавить("ЛишнийПараметр"); - - Попытка - ПараметрыПула = Менеджер.ИсточникДанных().ПулСоединений().ПараметрыКоннектора(); - Ожидаем.Что(ПараметрыПула, "Параметры дошли до пула").ИмеетДлину(1); - Ожидаем.Что(ПараметрыПула[0]).Равно("ПроизвольныйПараметр"); - Исключение - ИсточникПараметров.Закрыть(); - ВызватьИсключение; - КонецПопытки; - - ИсточникПараметров.Закрыть(); - -КонецПроцедуры // Соединение знает, начата ли им транзакция. // diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 54bff06..79cfd87 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -78,27 +78,43 @@ КонецПроцедуры +// Менеджер работает коннектором того типа, который задан источником: другого способа узнать, +// чем ходить в базу, у менеджера нет. &Тест Процедура МенеджерСоздаетсяИзИсточникаДанных() Экспорт - Параметры = Новый Массив; - Параметры.Добавить("параметр"); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", Параметры, 3); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник"); МенеджерСущностей = Новый МенеджерСущностей(Источник); - МенеджерСущностей.Инициализировать(); - Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); - Ожидаем.Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); - Ожидаем.Что(Пул.СтрокаСоединения()).Равно("источник"); - Ожидаем.Что(Пул.ПараметрыКоннектора()).Равно(Источник.ПараметрыКоннектора()); - Ожидаем.Что(Пул.РазмерПула()).Равно(3); + Ожидаем + .Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)"), "Коннектор источника") + .Равно(Тип("КоннекторInMemory")); МенеджерСущностей.Закрыть(); Источник.Закрыть(); КонецПроцедуры +// Массив вызывающего кода источнику не принадлежит: источник снимает с него копию, и правка +// после создания параметров источника не меняет. +&Тест +Процедура ПравкаМассиваПослеСозданияПараметровНеМеняет() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить("ПроизвольныйПараметр"); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "", Параметры); + + Параметры.Добавить("ЛишнийПараметр"); + + Ожидаем.Что(Источник.ПараметрыКоннектора(), "Лишний параметр в источник не попал").ИмеетДлину(1); + Ожидаем.Что(Источник.ПараметрыКоннектора()[0]).Равно("ПроизвольныйПараметр"); + + Источник.Закрыть(); + +КонецПроцедуры + // Параметры соединения задает только источник: менеджер их не принимает и сам источник // не создает. &Тест From be3ad9d946050b0a4668ada345fb8a535ca3c64f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:33:02 +0000 Subject: [PATCH 28/55] =?UTF-8?q?=D0=9F=D0=BE=D0=BC=D0=BE=D1=89=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20=D0=B4=D0=B2=D1=83=D1=85=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D1=85=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D1=8B=D0=B5=D0=A3=D1=82?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit МенеджерАвторов и ПодключитьКлассыСущностей были скопированы в ВнутреннийКонтрактИсточника и ПулИсточникаДанных, когда эти наборы добавлялись, и копии уже разошлись: в одной подключение классов было внутри МенеджерАвторов, в другой его звали снаружи. В общей версии подключение делает сам МенеджерАвторов - повторный вызов безвреден. Старые дубли между наборами не трогал: СтрокаСоединенияPostgres в пяти наборах и ДождатьсяЗадания в двух пришли не из этого PR. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\273\320\270\321\202\321\213.os" | 36 +++++++++++++++ ...21\207\320\275\320\270\320\272\320\260.os" | 40 ++--------------- ...20\260\320\275\320\275\321\213\321\205.os" | 44 +++++-------------- 3 files changed, 49 insertions(+), 71 deletions(-) diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index f6d3047..f59d69a 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -38,6 +38,42 @@ КонецФункции +// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. +// +// Повторное подключение безвредно: наборы зовут его перед каждым тестом. +// +Процедура ПодключитьКлассыСущностей() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + +КонецПроцедуры + +// Менеджер источника с моделью из Автора и того, на что Автор ссылается, и созданными таблицами. +// +// Параметры: +// Источник - ИсточникДанных - Источник. +// +// Возвращаемое значение: +// МенеджерСущностей - Менеджер с инициализированной моделью. +// +Функция МенеджерАвторов(Источник) Экспорт + + ПодключитьКлассыСущностей(); + + Менеджер = Новый МенеджерСущностей(Источник); + // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Возврат Менеджер; + +КонецФункции + // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 9331d19..ab0d85e 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -17,7 +17,7 @@ Процедура МенеджерВыдаетОдноХранилищеНаТип() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище менеджера"); - Менеджер = МенеджерАвторов(Источник); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); @@ -35,7 +35,7 @@ Процедура ОперацияХранилищаЗакрытогоИсточникаЗавершаетсяОшибкой() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище после закрытия"); - Менеджер = МенеджерАвторов(Источник); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); Источник.Закрыть(); @@ -70,7 +70,7 @@ Процедура СущностьСохраняетсяИЧитаетсяЧерезХранилище() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "операции через хранилище"); - Менеджер = МенеджерАвторов(Источник); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); Автор = Новый Автор; @@ -105,40 +105,6 @@ КонецПроцедуры -// Менеджер с типом Автор в модели, созданный из источника. -// -// Параметры: -// Источник - ИсточникДанных - Источник. -// -// Возвращаемое значение: -// МенеджерСущностей - Менеджер с инициализированной моделью. -// -Функция МенеджерАвторов(Источник) - - ПодключитьКлассыСущностей(); - - Менеджер = Новый МенеджерСущностей(Источник); - // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. - Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); - Менеджер.ДобавитьКлассВМодель(Тип("Автор")); - Менеджер.Инициализировать(); - - Возврат Менеджер; - -КонецФункции - -// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. -// -Процедура ПодключитьКлассыСущностей() - - ПодключитьСценарий( - ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), - "СущностьБезГенерируемогоИдентификатора" - ); - ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); - -КонецПроцедуры - // Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL // по переменным среды. // diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 18e7bc9..532bfc9 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -130,10 +130,10 @@ &Тест Процедура ЗакрытыйМенеджерХранилищНеВыдает() Экспорт - ПодключитьКлассыСущностей(); + ТестовыеУтилиты.ПодключитьКлассыСущностей(); Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища после закрытия"); - Менеджер = МенеджерАвторов(Источник); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); Менеджер.Закрыть(); @@ -163,10 +163,10 @@ &Тест Процедура ЗакрытиеМенеджераЗакрываетЕгоХранилища() Экспорт - ПодключитьКлассыСущностей(); + ТестовыеУтилиты.ПодключитьКлассыСущностей(); Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие хранилищ"); - Менеджер = МенеджерАвторов(Источник); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); Автор = Менеджер.СоздатьЭлемент(Тип("Автор")); Автор.Имя = "Иван"; @@ -190,11 +190,11 @@ &Тест Процедура ХранилищаПринадлежатМенеджеруАНеИсточнику() Экспорт - ПодключитьКлассыСущностей(); + ТестовыеУтилиты.ПодключитьКлассыСущностей(); Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища по менеджеру"); - Первый = МенеджерАвторов(Источник); - Второй = МенеджерАвторов(Источник); + Первый = ТестовыеУтилиты.МенеджерАвторов(Источник); + Второй = ТестовыеУтилиты.МенеджерАвторов(Источник); Ожидаем .Что(Первый.ПолучитьХранилищеСущностей(Тип("Автор")) = Второй.ПолучитьХранилищеСущностей(Тип("Автор")), @@ -211,11 +211,11 @@ &Тест Процедура ЗакрытиеМенеджераНеЛомаетДругойМенеджерИсточника() Экспорт - ПодключитьКлассыСущностей(); + ТестовыеУтилиты.ПодключитьКлассыСущностей(); Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "чужая модель"); - Первый = МенеджерАвторов(Источник); + Первый = ТестовыеУтилиты.МенеджерАвторов(Источник); Внешняя = Первый.СоздатьЭлемент(Тип("СущностьБезГенерируемогоИдентификатора")); Внешняя.ВнутреннийИдентификатор = 1; Первый.Сохранить(Внешняя); @@ -224,7 +224,7 @@ Автор.ВнешняяСущность = Внешняя; Первый.Сохранить(Автор); - Второй = МенеджерАвторов(Источник); + Второй = ТестовыеУтилиты.МенеджерАвторов(Источник); ХранилищеВторого = Второй.ПолучитьХранилищеСущностей(Тип("Автор")); Первый.Закрыть(); @@ -259,30 +259,6 @@ // Возвращаемое значение: // МенеджерСущностей - Менеджер с инициализированной моделью. // -Функция МенеджерАвторов(Источник) - - Менеджер = Новый МенеджерСущностей(Источник); - // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. - Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); - Менеджер.ДобавитьКлассВМодель(Тип("Автор")); - Менеджер.Инициализировать(); - - Возврат Менеджер; - -КонецФункции - -// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. -// -Процедура ПодключитьКлассыСущностей() - - ПодключитьСценарий( - ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), - "СущностьБезГенерируемогоИдентификатора" - ); - ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); - -КонецПроцедуры - // Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL // по переменным среды. // From 6ffbac1605a881486c1431f0d59e3b206482a357 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:38:29 +0000 Subject: [PATCH 29/55] =?UTF-8?q?=D0=98=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=A1=D0=A2=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=D0=BC=D0=B8=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=20=D0=A2=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B5=D0=A3=D1=82=D0=B8=D0=BB=D0=B8=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Третий помощник, скопированный при добавлении двух новых наборов. Тела совпадали дословно; в общей версии переменные среды читаются напрямую, без префикса модуля. Заодно убран осиротевший комментарий от МенеджерАвторов: при выносе предыдущих двух помощников тело функции удалилось, а ее описание осталось висеть над соседней функцией. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\273\320\270\321\202\321\213.os" | 28 ++++++++++++++ ...21\207\320\275\320\270\320\272\320\260.os" | 29 +------------- ...20\260\320\275\320\275\321\213\321\205.os" | 38 +------------------ 3 files changed, 30 insertions(+), 65 deletions(-) diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index f59d69a..d5301b1 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -74,6 +74,34 @@ КонецФункции +// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL +// по переменным среды. +// +// Возвращаемое значение: +// ИсточникДанных - Источник. +// +Функция ИсточникСТранзакциями() Экспорт + + Если ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); + КонецЕсли; + + Если ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда + СтрокаСоединения = СтрШаблон( + "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") + ); + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + КонецЕсли; + + ВызватьИсключение "Нет доступного коннектора с транзакциями"; + +КонецФункции + // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index ab0d85e..2a791e5 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -92,7 +92,7 @@ &Тест Процедура ТранзакцияПотокаОткрываетсяЧерезИсточник() Экспорт - Источник = ИсточникСТранзакциями(); + Источник = ТестовыеУтилиты.ИсточникСТранзакциями(); РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); Первый = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> Коннектор"); @@ -105,30 +105,3 @@ КонецПроцедуры -// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL -// по переменным среды. -// -// Возвращаемое значение: -// ИсточникДанных - Источник. -// -Функция ИсточникСТранзакциями() - - Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда - Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); - КонецЕсли; - - Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда - СтрокаСоединения = СтрШаблон( - "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") - ); - Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); - КонецЕсли; - - ВызватьИсключение "Нет доступного коннектора с транзакциями"; - -КонецФункции diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 532bfc9..58e591f 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -43,7 +43,7 @@ &Тест Процедура ТранзакцияПотокаОбщаяДляМенеджеровИсточника() Экспорт - Источник = ИсточникСТранзакциями(); + Источник = ТестовыеУтилиты.ИсточникСТранзакциями(); Первый = Новый МенеджерСущностей(Источник); Второй = Новый МенеджерСущностей(Источник); @@ -251,42 +251,6 @@ КонецПроцедуры -// Менеджер с типом Автор в модели, созданный из источника. -// -// Параметры: -// Источник - ИсточникДанных - Источник. -// -// Возвращаемое значение: -// МенеджерСущностей - Менеджер с инициализированной моделью. -// -// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL -// по переменным среды. -// -// Возвращаемое значение: -// ИсточникДанных - Источник. -// -Функция ИсточникСТранзакциями() - - Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда - Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); - КонецЕсли; - - Если ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда - СтрокаСоединения = СтрШаблон( - "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), - ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") - ); - Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); - КонецЕсли; - - ВызватьИсключение "Нет доступного коннектора с транзакциями"; - -КонецФункции - // Создает источник и открывает соединение в потоке фонового задания. Используется тестом // "ИсточникЗакрываетсяПоЗавершениюПотока". // From a564de05ce630bf63489b197835453d062334b3d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:44:51 +0000 Subject: [PATCH 30/55] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20Sonar?= =?UTF-8?q?=20=D0=BE=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D0=BD=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Диагностики про дубли литералов и захардкоженный адрес были сняты в шапках наборов, из которых помощники переехали; в ТестовыеУтилиты шапки не было, и на новом месте они всплыли. Подавления перенесены вместе с кодом. Плюс два места с лишними пустыми строками, оставшимися после удаления функций: в ВнутреннийКонтрактИсточника и в ДоступКоннектором. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...3\321\202\320\270\320\273\320\270\321\202\321\213.os" | 9 +++++++++ ...2\320\276\321\207\320\275\320\270\320\272\320\260.os" | 1 - ...5\320\272\321\202\320\276\321\200\320\276\320\274.os" | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index d5301b1..3e2b726 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -1,3 +1,12 @@ +// Помощники, общие для тестовых наборов. +// +// Значения по умолчанию для переменных среды - localhost и postgres - повторяются и выглядят +// для анализатора захардкоженным адресом и дублем литерала, поэтому обе диагностики здесь сняты: +// в наборах, откуда эти помощники переехали, они были сняты по той же причине. +// +// BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off + Функция ПолучитьПеременнуюСредыИлиЗначение(ИмяПеременной, ЗначениеПоУмолчанию) Экспорт ПеременнаяСреды = ПолучитьПеременнуюСреды(ИмяПеременной); Если ПеременнаяСреды = Неопределено Тогда diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" index 2a791e5..da4bc0a 100644 --- "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -104,4 +104,3 @@ Источник.Закрыть(); КонецПроцедуры - diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index ae26b1a..140fcaf 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -267,7 +267,6 @@ КонецПроцедуры - // Соединение знает, начата ли им транзакция. // &Тест From 1ed6e4299242b7064e2debbca4bc1d1e93b8c5c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 08:56:11 +0000 Subject: [PATCH 31/55] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=20=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=B3=D0=B5=D1=82=D1=82=D0=B5=D1=80=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D0=BA=D0=B0=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверяется контракт пары УстановитьАвтоЗакрытие/АвтоЗакрытие: у нового источника признак включен, после выключения сеттером геттер отдает Ложь. Поведение автозакрытия проверялось фоновыми заданиями, сам геттер тестами закрыт не был. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\320\260\320\275\320\275\321\213\321\205.os" | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 79cfd87..86a23aa 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -115,6 +115,23 @@ КонецПроцедуры +// Геттер автозакрытия показывает, закроется ли источник по завершению создавшего его потока: +// по этому признаку владелец источника узнает, управляет ли он временем жизни источника сам. +&Тест +Процедура АвтоЗакрытиеВключеноПоУмолчаниюИЧитаетсяПослеСеттера() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "признак автозакрытия"); + + Ожидаем.Что(Источник.АвтоЗакрытие(), "Новый источник закрывается по завершению потока").ЭтоИстина(); + + Источник.УстановитьАвтоЗакрытие(Ложь); + + Ожидаем.Что(Источник.АвтоЗакрытие(), "После сеттера источник переживает поток").ЭтоЛожь(); + + Источник.Закрыть(); + +КонецПроцедуры + // Параметры соединения задает только источник: менеджер их не принимает и сам источник // не создает. &Тест From 8b84d17e838235236be8124cba2e2e7f63d65ea1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 09:09:23 +0000 Subject: [PATCH 32/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B2=20=D1=81=D1=82=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B5=D1=81=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Диагностика Typo проверяет строковые литералы, и слово "автозакрытия" в строке соединения нового теста она не знает. Смысл строки не меняется: это произвольное имя базы в памяти. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 86a23aa..40d7bd0 100644 --- "a/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -120,7 +120,7 @@ &Тест Процедура АвтоЗакрытиеВключеноПоУмолчаниюИЧитаетсяПослеСеттера() Экспорт - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "признак автозакрытия"); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие по завершению потока"); Ожидаем.Что(Источник.АвтоЗакрытие(), "Новый источник закрывается по завершению потока").ЭтоИстина(); From e58bf2eac70f8b8390224fa1d7efca68d84b1036 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 09:55:23 +0000 Subject: [PATCH 33/55] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=83=D0=BB=D0=B0=20=D0=BD=D0=B5=20=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D1=8F=D0=B5=D1=82=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5,=20=D0=BD=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=BC=20=D1=83=D0=BF=D0=B0=D0=BB=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Свободное соединение изымалось из очереди до закрытия, а заявка снималась до закрытия своего соединения. Если закрытие падало, соединение не лежало больше нигде: повтор его не находил, доводил закрытие до конца и отчитывался об успехе, а соединение с БД оставалось открытым навсегда. Теперь соединение выбывает со своего места после того, как закрыто, - как уже сделано для занятых, - и повтор пробует его снова. Место закрытого соединения возвращается пулу тут же, а не общим обнулением счетчика в конце. Иначе после неудачного закрытия пул считал занятыми места уже закрытых соединений, и следующий захват ждал предел ожидания впустую: вернуть соединение было некому. Тест на повторное закрытие источника проверял прежний контракт - что упавшее соединение из очереди выбыло, - поэтому переписан: повтор закрывает то же соединение. План закрытий коннектора получил число падений, чтобы удачный повтор вообще был возможен. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 23 ++- ...20\272\321\202\320\276\321\200\320\260.os" | 18 ++- ...20\270\320\271\320\241\320\221\320\224.os" | 152 +++++++++++++++++- 3 files changed, 181 insertions(+), 12 deletions(-) diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 71d2d36..ff536cf 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -363,10 +363,12 @@ Попытка - // Соединение выбывает из соответствия сразу после того, как закрыто, - так же, как выбывает - // из очереди свободное. Если закрытие споткнется на следующем, уже закрытые ни одному - // потоку не достанутся, а повтор закроет остаток. Соединение, на котором закрытие упало, - // остается за потоком: его состояние неизвестно, и повтор должен попробовать снова. + // Соединение выбывает со своего места - из соответствия, из очереди свободных, из заявки - + // после того, как закрыто, и тут же возвращает пулу занятое им место. Споткнись закрытие + // на соединении, оно остается там, где лежало: его состояние неизвестно, и повтор должен + // попробовать снова. Изыми его раньше - и повтор прошел бы мимо, отчитался об успехе, а + // соединение с БД осталось бы открытым. Уже закрытые при этом никому не достанутся, а + // освобожденные места не заставят следующий захват ждать того, чего не будет. Для Каждого ПотокИсполнения Из ПотокиСЗанятымиСоединениями() Цикл СоединенияПотоковИсполнения.Получить(ПотокИсполнения).Закрыть(); СоединенияПотоковИсполнения.Удалить(ПотокИсполнения); @@ -375,22 +377,27 @@ КонецЦикла; Пока Свободные.Количество() > 0 Цикл - Свободные.Взять().Получить().Закрыть(); + Свободные.Подсмотреть().Получить().Закрыть(); + Свободные.Взять(); + СозданоСоединений = СозданоСоединений - 1; КонецЦикла; // Соединение, уже отданное заявке, не лежит ни за потоком, ни в свободных: заберет его // ожидающий, до которого очередь дошла. Без разбора заявок оно пережило бы закрытие пула. Пока Ожидающие.Количество() > 0 Цикл - Заявка = Ожидающие.Взять().Получить(); - Заявка.Снята = Истина; + Заявка = Ожидающие.Подсмотреть().Получить(); Если Заявка.Соединение <> Неопределено Тогда Заявка.Соединение.Закрыть(); Заявка.Соединение = Неопределено; + СозданоСоединений = СозданоСоединений - 1; КонецЕсли; + Заявка.Снята = Истина; + Ожидающие.Взять(); КонецЦикла; // Брони снимаются последними: по снятой броне открывающий поток исполнения поймет, что пул - // закрыт, и закроет свое соединение сам - здесь его еще нет. + // закрыт, и закроет свое соединение сам - здесь его еще нет. Их места пулу возвращает + // обнуление счетчика: закрытые соединения свои вернули уже сами. Брони.Очистить(); СозданоСоединений = 0; diff --git "a/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" index 5924d3f..87cde72 100644 --- "a/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" +++ "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -7,6 +7,8 @@ // Число - Номер закрытия, начиная с которого закрытие завершается ошибкой. Перем ПадатьНачинаяС; +// Число - Сколько закрытий подряд падает, начиная с этого номера. Ноль - падают все следующие. +Перем КоличествоПадений; // Число - Сколько закрытий уже выполнено по этому плану. Перем ВыполненоЗакрытий; @@ -15,10 +17,14 @@ // Параметры: // ПНомерПервогоПадения - Число - Номер закрытия, с которого начинаются ошибки. Первое закрытие // имеет номер 1. +// ПКоличествоПадений - Число - Сколько закрытий подряд падает. Ноль - все закрытия начиная +// с первого падения. Конечное число нужно там, где проверяется удачный повтор закрытия: +// пул повторяет закрытие того же соединения, и вечно падающий коннектор закрыть его не даст. // -Процедура ПриСозданииОбъекта(Знач ПНомерПервогоПадения) +Процедура ПриСозданииОбъекта(Знач ПНомерПервогоПадения, Знач ПКоличествоПадений = 0) ПадатьНачинаяС = ПНомерПервогоПадения; + КоличествоПадений = ПКоличествоПадений; ВыполненоЗакрытий = 0; КонецПроцедуры @@ -32,6 +38,14 @@ ВыполненоЗакрытий = ВыполненоЗакрытий + 1; - Возврат ВыполненоЗакрытий >= ПадатьНачинаяС; + Если ВыполненоЗакрытий < ПадатьНачинаяС Тогда + Возврат Ложь; + КонецЕсли; + + Если КоличествоПадений = 0 Тогда + Возврат Истина; + КонецЕсли; + + Возврат ВыполненоЗакрытий < ПадатьНачинаяС + КоличествоПадений; КонецФункции diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 59f5d97..5a6aaab 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -701,7 +701,7 @@ "Падающий при закрытии", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Истина), + ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), 1 ); ПулПадающий = ИсточникПадающий.ПулСоединений(); @@ -715,7 +715,8 @@ Ожидаем.Что(ИсточникПадающий.Закрыт(), "Источник не считается закрытым после ошибки").ЭтоЛожь(); - // Соединение, на котором споткнулось закрытие, из очереди уже выбыло: повтор закрывает остаток. + // Соединение, на котором споткнулось закрытие, осталось в пуле: повтор закрывает его же, и на + // этот раз удачно - по плану падает только первое закрытие. ИсточникПадающий.Закрыть(); Ожидаем.Что(ИсточникПадающий.Закрыт(), "Повторное закрытие доводит дело до конца").ЭтоИстина(); @@ -776,6 +777,51 @@ КонецПроцедуры +// Свободное соединение, на котором закрытие споткнулось, из очереди свободных не выбывает: иначе +// повтор закрытия его уже не найдет, отчитается об успехе, а соединение с БД останется открытым, +// и закрыть его будет некому. Занятые ведут себя так же - соединение остается за своим потоком. +// +// Потоки исполнения подставлены поддельные: два свободных соединения в одном потоке не получить, +// а проверяется здесь очередь свободных, а не конкуренция потоков. +// +&Тест +Процедура НеудачноеЗакрытиеНеТеряетСвободноеСоединение() Экспорт + + ПулПадающий = ПулСДвумяСвободнымиСоединениями(2); + + Ожидаем + .Что(ПулПадающий, "Закрытие споткнулось на втором свободном соединении") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем + .Что(ПулПадающий.КоличествоСвободных(), "Соединение, на котором упало закрытие, осталось в пуле") + .Равно(1); + +КонецПроцедуры + +// Место закрытого соединения возвращается пулу сразу, как соединение закрыто: счетчик занятых +// мест считает живые соединения, а не когда-то открытые. Иначе после неудачного закрытия пул +// остается с местами, занятыми уже закрытыми соединениями, и следующий захват ждет впустую: +// вернуть соединение некому. +// +&Тест +Процедура НеудачноеЗакрытиеОсвобождаетМестоЗакрытогоСоединения() Экспорт + + ПулПадающий = ПулСДвумяСвободнымиСоединениями(2); + + Попытка + ПулПадающий.Закрыть(); + Исключение + // Ошибка закрытия здесь ожидаема и проверяется отдельным тестом. + КонецПопытки; + + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место закрытого соединения освобождено") + .Равно(1); + +КонецПроцедуры + // Закрытие пула терминально. Иначе ожидающий, проснувшийся после закрытия, молча открыл бы // себе новое соединение, которое пережило бы закрытие пула. // @@ -843,6 +889,64 @@ КонецПроцедуры +// Соединение, назначенное заявке, при ошибке закрытия из очереди ожидающих не выбывает - как не +// выбывают свободное и занятое. Иначе повтор закрытия прошел бы мимо него, а соединение с БД +// осталось бы открытым. +// +&Тест +Процедура НеудачноеЗакрытиеНеТеряетСоединениеЗаявки() Экспорт + + ПулСОжиданием = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), + 30000, + 5000 + ); + + // Единственное соединение занято, поэтому фоновое задание встанет в очередь. + ПулСОжиданием.Занять(); + + Попытка + ПроверитьСохранностьСоединенияЗаявки(); + Исключение + ПулСОжиданием.Закрыть(); + ВызватьИсключение; + КонецПопытки; + +КонецПроцедуры + +// Отдает соединение заявке фонового задания и закрывает пул, пока задание спит между проверками +// своей заявки. +// +Процедура ПроверитьСохранностьСоединенияЗаявки() + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьИзПулаСОжиданиемВФоне", Новый Массив, Истина); + + ДождатьсяОжидающего(); + + // Освобожденное соединение уходит заявке задания и до его пробуждения лежит только в очереди + // ожидающих: там его и находит закрытие пула. + ПулСОжиданием.Освободить(); + + Ожидаем + .Что(ПулСОжиданием, "Закрытие споткнулось на соединении заявки") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем + .Что(ПулСОжиданием.КоличествоОжидающих(), "Заявка с незакрытым соединением осталась в очереди") + .Равно(1); + + // По плану падает только первое закрытие: повтор закрывает то же соединение и доводит дело + // до конца. + ПулСОжиданием.Закрыть(); + + ДождатьсяЗадания(Задание); + +КонецПроцедуры + // Ждет, пока фоновое задание встанет в очередь ожидающих. // Процедура ДождатьсяОжидающего() @@ -910,6 +1014,50 @@ ПулСОжиданием.Занять(); КонецПроцедуры +// Пул на два соединения, оба занятые и сразу освобожденные: обе штуки лежат в очереди свободных. +// +// Потоки исполнения поддельные: захват реентерабельный, и в одном потоке второго соединения +// не получить. Предел ожидания короткий, чтобы тест не стоял на нем полминуты. +// +// Параметры: +// НомерПадающегоЗакрытия - Число - Номер закрытия, начиная с которого коннектор падает. +// +// Возвращаемое значение: +// ПулСоединенийСБД - Пул с двумя свободными соединениями. +// +Функция ПулСДвумяСвободнымиСоединениями(Знач НомерПадающегоЗакрытия) + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + + ПулДвухСоединений = Новый ПулСоединенийСБД( + 2, + Тип("КоннекторУправляемоеОткрытие"), + "", + ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия)), + 1000, + 10, + ПоддельныеПотоки + ); + + ПервыйПоток = Мок.Получить(Тип("ПотокИсполнения")); + ВторойПоток = Мок.Получить(Тип("ПотокИсполнения")); + + // Соединения занимаются оба сразу: освободи первый поток свое до захвата второго, второй взял + // бы то же самое из свободных, и второго соединения в пуле не появилось бы. + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулДвухСоединений.Занять(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + ПулДвухСоединений.Занять(); + ПулДвухСоединений.Освободить(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулДвухСоединений.Освободить(); + + Возврат ПулДвухСоединений; + +КонецФункции + // Собирает параметры коннектора для КоннекторУправляемоеОткрытие. // // Параметры: From 561c41d9a757151cad88a72107606a5f20301744 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 09:59:32 +0000 Subject: [PATCH 34/55] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B5=20=D0=BD=D0=B5=D0=B7=D0=B0=D1=80=D0=B5=D0=B3?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=82=D0=B8=D0=BF=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=B5=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=B8=20=D0=BD=D0=B5=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D1=83?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Менеджер выдавал хранилище с Неопределено вместо модели, если тип не был зарегистрирован, и клал его в кэш: следующая регистрация класса уже ничего не меняла - то же нерабочее хранилище выдавалось до закрытия менеджера, а падало оно позже и не о том. Проверка модели перенесена в момент выдачи хранилища, как и просил TODO рядом с ней, и идет до вставки в кэш. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\276\321\201\321\202\320\265\320\271.md" | 3 +- ...20\276\321\200\320\260\320\274\320\270.os" | 16 +++++++-- ...20\276\321\201\321\202\320\265\320\271.os" | 9 +++-- ...20\260\320\275\320\275\321\213\321\205.os" | 36 +++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 06a4488..fe25810 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -224,7 +224,8 @@ // // Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности // менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается -// ошибкой. +// ошибкой, как и обращение за типом, не зарегистрированным в модели: такой тип можно +// зарегистрировать и получить хранилище снова. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 0a19bf0..b46856b 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -645,8 +645,20 @@ КонецПроцедуры -Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) - // TODO: проверка должна быть в момент получения репозитория +// @internal +// +// Для служебного пользования. +// +// Проверяет, что тип сущности зарегистрирован в модели данных менеджера. +// +// Зовется в момент выдачи хранилища: хранилище без модели нерабочее, и ошибка о незарегистрированном +// типе должна прийти оттуда, где тип еще виден, а не из середины операции над данными. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель типа сущности. +// Неопределено - Тип в модели не зарегистрирован. +// +Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) Экспорт Ожидаем.Что(ОбъектМодели, "Тип сущности не зарегистрирован в модели данных").Не_().Равно(Неопределено); КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index bf4dcde..111a3a9 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -341,7 +341,8 @@ // // Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности // менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается -// ошибкой. +// ошибкой, как и обращение за типом, не зарегистрированным в модели: такой тип можно +// зарегистрировать и получить хранилище снова. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели @@ -368,7 +369,11 @@ // Пока ждали запись, хранилище мог создать другой поток. Хранилище = Хранилища.Получить(ТипСущности); Если Хранилище = Неопределено Тогда - Хранилище = Новый ХранилищеСущностей(МодельДанных.Получить(ТипСущности), ЭтотОбъект); + ОбъектМодели = МодельДанных.Получить(ТипСущности); + // Проверка идет до вставки в кэш: хранилище без модели нерабочее, и закэшируй его + // менеджер - регистрация класса следом уже ничего бы не исправила. + РаботаСКоннекторами.ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); + Хранилище = Новый ХранилищеСущностей(ОбъектМодели, ЭтотОбъект); Хранилища.Вставить(ТипСущности, Хранилище); КонецЕсли; diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 58e591f..32c31ce 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -124,6 +124,42 @@ КонецПроцедуры +// Хранилище выдается только для типа, зарегистрированного в модели менеджера: без модели хранилище +// не работает, а падало бы оно уже внутри операции и не о том. Несозданное хранилище и в кэш не +// попадает, поэтому после регистрации класса менеджер выдает рабочее. +&Тест +Процедура ХранилищеНезарегистрированногоТипаНеВыдается() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без модели"); + Менеджер = Новый МенеджерСущностей(Источник); + + ТекстОшибки = ""; + Попытка + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем + .Что(ТекстОшибки, "Хранилище незарегистрированного типа не выдается") + .Содержит("не зарегистрирован в модели данных"); + + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Ожидаем + .Что(Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")).ПолучитьОбъектМодели() = Неопределено, + "После регистрации класса менеджер выдает хранилище с моделью") + .ЭтоЛожь(); + + Менеджер.Закрыть(); + Источник.Закрыть(); + +КонецПроцедуры + // Закрытие менеджера терминально: хранилищ он больше не выдает. Иначе поток, ожидавший создания // хранилища, создал бы его уже после закрытия, и это хранилище пережило бы менеджер вместе с его // очищенной моделью. From 7e00039e50d3d378166ac52ed803b09e876580a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 10:00:52 +0000 Subject: [PATCH 35/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=BF=D1=83=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=B1=D0=BB=D0=BE=D0=BA=20=D0=98=D1=81=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Закрытие роняется тем же ассертом, что и в соседнем тесте, - Попытка с пустым Исключение не нужна. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...65\320\275\320\270\320\271\320\241\320\221\320\224.os" | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 5a6aaab..7e14721 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -810,11 +810,9 @@ ПулПадающий = ПулСДвумяСвободнымиСоединениями(2); - Попытка - ПулПадающий.Закрыть(); - Исключение - // Ошибка закрытия здесь ожидаема и проверяется отдельным тестом. - КонецПопытки; + // Первое соединение закрывается, на втором закрытие падает - что оно падает, проверяет соседний + // тест, здесь это лишь способ остановить закрытие на полпути. + Ожидаем.Что(ПулПадающий).Метод("Закрыть").ВыбрасываетИсключение("Закрытие соединения не удалось"); Ожидаем .Что(ПулПадающий.КоличествоСозданных(), "Место закрытого соединения освобождено") From 1601a41c545deb3752c7878ac6e2ec0a6805ca59 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 10:04:46 +0000 Subject: [PATCH 36/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D1=81=D0=BB=D1=83=D0=B6=D0=B5?= =?UTF-8?q?=D0=B1=D0=BD=D1=8B=D0=B9=20=D1=82=D0=B5=D0=B3=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=D0=BB=D0=B5=20=D0=B2=D0=BD=D1=83=D1=82=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=B5=D0=B3=D0=BE=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Модуль лежит в internal целиком, соседние процедуры пометки @internal не носят, поэтому она снята, а не подавлена диагностикой. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\272\321\202\320\276\321\200\320\260\320\274\320\270.os" | 4 ---- 1 file changed, 4 deletions(-) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index b46856b..3e85506 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -645,10 +645,6 @@ КонецПроцедуры -// @internal -// -// Для служебного пользования. -// // Проверяет, что тип сущности зарегистрирован в модели данных менеджера. // // Зовется в момент выдачи хранилища: хранилище без модели нерабочее, и ошибка о незарегистрированном From f3e77f4a90b988ab1e40dfe8be8d6076dae99be8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 11:34:05 +0000 Subject: [PATCH 37/55] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D1=82=D0=B8=D0=BF=D0=B0=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B5=D1=85=D0=B0=D0=BB=D0=B0=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=20=D1=81=D1=83=D1=89?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверка о модели данных жила в модуле работы с коннекторами и оттуда экспортировалась менеджеру. Ее единственный вызов внутри модуля - в Сохранить - после переноса проверки в момент выдачи хранилища стал недостижим: все пути к операции идут через ПолучитьХранилищеСущностей, который падает раньше. Проверка стала приватной процедурой менеджера рядом с проверкой закрытия, из внутреннего модуля убрана вместе с мертвым вызовом. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\320\276\321\200\320\260\320\274\320\270.os" | 14 -------------- ...\320\276\321\201\321\202\320\265\320\271.os" | 17 ++++++++++++++--- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 3e85506..17b0ec3 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -28,7 +28,6 @@ Процедура Сохранить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт ПроверитьЧтоКлассЯвляетсяСущностью(АктивнаяЗапись.ТипСущности(Сущность)); - ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); Пул = Менеджер.ИсточникДанных().ПулСоединений(); @@ -645,19 +644,6 @@ КонецПроцедуры -// Проверяет, что тип сущности зарегистрирован в модели данных менеджера. -// -// Зовется в момент выдачи хранилища: хранилище без модели нерабочее, и ошибка о незарегистрированном -// типе должна прийти оттуда, где тип еще виден, а не из середины операции над данными. -// -// Параметры: -// ОбъектМодели - ОбъектМодели - Модель типа сущности. -// Неопределено - Тип в модели не зарегистрирован. -// -Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) Экспорт - Ожидаем.Что(ОбъектМодели, "Тип сущности не зарегистрирован в модели данных").Не_().Равно(Неопределено); -КонецПроцедуры - Процедура ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность) Если ОбъектМодели.Идентификатор().ГенерируемоеЗначение Тогда Возврат; diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 111a3a9..822b7ea 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -370,9 +370,7 @@ Хранилище = Хранилища.Получить(ТипСущности); Если Хранилище = Неопределено Тогда ОбъектМодели = МодельДанных.Получить(ТипСущности); - // Проверка идет до вставки в кэш: хранилище без модели нерабочее, и закэшируй его - // менеджер - регистрация класса следом уже ничего бы не исправила. - РаботаСКоннекторами.ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); + ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); Хранилище = Новый ХранилищеСущностей(ОбъектМодели, ЭтотОбъект); Хранилища.Вставить(ТипСущности, Хранилище); КонецЕсли; @@ -399,6 +397,19 @@ Ожидаем.Что(Закрыт, "Менеджер сущностей закрыт").ЭтоЛожь(); КонецПроцедуры +// Проверяет, что тип сущности зарегистрирован в модели данных менеджера. +// +// Вызывается до вставки хранилища в кэш: хранилище без модели нерабочее, и закэшируй его +// менеджер - регистрация класса следом уже ничего бы не исправила. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель типа сущности. +// Неопределено - Тип в модели не зарегистрирован. +// +Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) + Ожидаем.Что(ОбъектМодели, "Тип сущности не зарегистрирован в модели данных").Не_().Равно(Неопределено); +КонецПроцедуры + // @internal // // Для служебного пользования. From 4f695a2133d2b21f4009cbd798792dd451476d99 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 13:26:23 +0000 Subject: [PATCH 38/55] =?UTF-8?q?=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=20=D0=BE=D0=B1=D1=80=D1=8B=D0=B2=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BE=D1=81=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=87=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Движок обходит подписчиков завершения потока одним циклом и ошибки обработчиков не перехватывает, поэтому исключение из закрытия источника оставляло без обработки всех, кто подписался позже: их источники не закрывались, а пулы не получали назад соединения завершившегося потока. Обработчик ловит ошибку закрытия и пишет ее в лог: поток кончился, и сообщать о ней больше некому. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.os" | 27 +++++++++- ...20\260\320\275\320\275\321\213\321\205.os" | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index f7068a5..d498058 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -2,6 +2,7 @@ #Использовать asserts #Использовать atomic +#Использовать logos #Использовать reflector // Строка - Имя источника данных, по которому сущности выбирают источник в аннотации &Сущность. @@ -22,6 +23,9 @@ // АтомарноеБулево - Источник закрыт: пул закрыт и соединений больше не выдает. Атомарное: // закрывает один поток, читают все, кто работает через источник. Перем пЗакрыт; +// Лог - Журнал источника: в него уходит ошибка закрытия по завершению потока, жаловаться о которой +// уже некому. +Перем Лог; // Вызывается движком по завершению потока, создавшего источник: при включенном автозакрытии // закрывает источник. Объявлен до конструктора намеренно: имя обработчика движок разрешает @@ -31,9 +35,26 @@ // // BSLLS:UnusedLocalMethod-off Процедура ПриЗавершенииПотока() - Если пАвтоЗакрытие.Получить() Тогда - Закрыть(); + + Если НЕ пАвтоЗакрытие.Получить() Тогда + Возврат; КонецЕсли; + + // Подписчиков у завершения потока столько, сколько источников и пулов его застали, и движок + // обходит их одним циклом, не перехватывая ошибки обработчиков. Выпусти этот обработчик + // исключение, и до подписавшихся позже очередь не дойдет: их источники не закроются, а пулы + // не получат назад соединения завершившегося потока. Ошибка уходит в лог: поток кончился, + // и сообщать о ней больше некому. + Попытка + Закрыть(); + Исключение + Лог.Ошибка( + "Не удалось закрыть источник данных ""%1"" по завершению создавшего его потока: %2", + пИмя, + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + КонецПопытки; + КонецПроцедуры // BSLLS:UnusedLocalMethod-on @@ -69,6 +90,8 @@ ПроверитьПоддержкуИнтерфейсаКоннектора(ТипКоннектора); + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.datasource"); + пИмя = Имя; пТипКоннектора = ТипКоннектора; пСтрокаСоединения = СтрокаСоединения; diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 32c31ce..74bdf92 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -12,6 +12,9 @@ // ИсточникДанных - Источник, созданный в другом потоке; поток теста проверяет его после // завершения потока создания. Другой поток дает фоновое задание. Перем ИсточникДругогоПотока; +// ИсточникДанных - Второй источник того же потока: он подписывается на завершение потока позже +// первого, поэтому по нему видно, дошла ли до него очередь обработчиков. +Перем ВторойИсточникДругогоПотока; &Тест Процедура МенеджерыОдногоИсточникаДелятПул() Экспорт @@ -124,6 +127,28 @@ КонецПроцедуры +// Обработчик завершения потока - один из многих: движок обходит подписчиков события в порядке +// подписки и ошибки обработчиков не перехватывает. Выпусти наш обработчик исключение наружу, и +// подписавшиеся позже остались бы необработанными: источники того же потока не закрылись бы, +// а пулы не получили назад соединения завершившегося потока. +&Тест +Процедура ОшибкаЗакрытияНеЛишаетАвтоЗакрытияОстальныеИсточники() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), + "КоннекторУправляемоеОткрытие" + ); + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьДваИсточникаВДругомПотоке", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем + .Что(ВторойИсточникДругогоПотока.Закрыт(), "Источник, подписавшийся после падающего, закрыт") + .ЭтоИстина(); + +КонецПроцедуры + // Хранилище выдается только для типа, зарегистрированного в модели менеджера: без модели хранилище // не работает, а падало бы оно уже внутри операции и не о том. Несозданное хранилище и в кэш не // попадает, поэтому после регистрации класса менеджер выдает рабочее. @@ -298,6 +323,34 @@ КонецПроцедуры +// Создает в потоке фонового задания два источника: первый на коннекторе, который падает при +// закрытии, второй обычный. Используется тестом +// "ОшибкаЗакрытияНеЛишаетАвтоЗакрытияОстальныеИсточники". +// +// Соединение первому источнику открывается намеренно: без открытого соединения закрывать в пуле +// было бы нечего и ошибка закрытия не возникла бы. +// +Процедура СоздатьДваИсточникаВДругомПотоке() Экспорт + + ПараметрыПадающегоКоннектора = Новый Массив; + ПараметрыПадающегоКоннектора.Добавить(0); + ПараметрыПадающегоКоннектора.Добавить(Ложь); + ПараметрыПадающегоКоннектора.Добавить(Ложь); + ПараметрыПадающегоКоннектора.Добавить(Истина); + + ИсточникДругогоПотока = Новый ИсточникДанных( + "Падающий при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + ПараметрыПадающегоКоннектора + ); + МенеджерПадающего = Новый МенеджерСущностей(ИсточникДругогоПотока); + МенеджерПадающего.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + ВторойИсточникДругогоПотока = Новый ИсточникДанных("Второй", Тип("КоннекторInMemory"), "второй источник потока"); + +КонецПроцедуры + // Создает источник без автоматического закрытия и открывает соединение в потоке фонового задания. Используется // тестом "АвтоЗакрытиеОтключаетсяСеттером". // From 397e56a41b7cbd8a3b4c3e8d0fd04aac4e98a82c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 13:45:59 +0000 Subject: [PATCH 39/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BD=D0=BD=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B0=20=D1=81=D0=BE=D0=B1=D0=B8=D1=80=D0=B0=D1=8E?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Второй набор собирал тот же массив параметров вручную, и повторяющиеся литералы Ложь заметил анализатор. Помощник переехал в ТестовыеУтилиты, оба набора зовут его. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\273\320\270\321\202\321\213.os" | 31 ++++++++++++ ...20\260\320\275\320\275\321\213\321\205.os" | 8 +--- ...20\270\320\271\320\241\320\221\320\224.os" | 47 ++++--------------- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index 3e2b726..25df1c6 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -111,6 +111,37 @@ КонецФункции +// Собирает параметры коннектора для КоннекторУправляемоеОткрытие. +// +// Параметры: +// ПаузаОткрытия - Число - Пауза внутри открытия в миллисекундах. +// ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. +// ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. +// ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. +// ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. +// +// Возвращаемое значение: +// Массив - Параметры коннектора. +// +Функция ПараметрыТестовогоКоннектора( + Знач ПаузаОткрытия, + Знач ПадатьПриОткрытии, + Знач ПадатьПриЗавершенииТранзакции = Ложь, + Знач ПадатьПриЗакрытии = Ложь, + Знач ПланЗакрытий = Неопределено +) Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(ПаузаОткрытия); + Параметры.Добавить(ПадатьПриОткрытии); + Параметры.Добавить(ПадатьПриЗавершенииТранзакции); + Параметры.Добавить(ПадатьПриЗакрытии); + Параметры.Добавить(ПланЗакрытий); + + Возврат Параметры; + +КонецФункции + // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 74bdf92..155b5f0 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -332,17 +332,11 @@ // Процедура СоздатьДваИсточникаВДругомПотоке() Экспорт - ПараметрыПадающегоКоннектора = Новый Массив; - ПараметрыПадающегоКоннектора.Добавить(0); - ПараметрыПадающегоКоннектора.Добавить(Ложь); - ПараметрыПадающегоКоннектора.Добавить(Ложь); - ПараметрыПадающегоКоннектора.Добавить(Истина); - ИсточникДругогоПотока = Новый ИсточникДанных( "Падающий при закрытии", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыПадающегоКоннектора + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Истина) ); МенеджерПадающего = Новый МенеджерСущностей(ИсточникДругогоПотока); МенеджерПадающего.ВычислитьСКоннектором("Коннектор -> Коннектор"); diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 7e14721..8aff94b 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -519,7 +519,7 @@ 2, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(3000, Ложь) + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(3000, Ложь) ); Попытка @@ -596,7 +596,7 @@ 1, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(3000, Ложь) + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(3000, Ложь) ); Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьМедленноеВФоне", Новый Массив, Истина); @@ -625,7 +625,7 @@ 1, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Истина) + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Истина) ); Попытка @@ -658,7 +658,7 @@ "Падающий", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Истина), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Истина), 1 ); ПулПадающий = ИсточникПадающий.ПулСоединений(); @@ -701,7 +701,7 @@ "Падающий при закрытии", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), 1 ); ПулПадающий = ИсточникПадающий.ПулСоединений(); @@ -741,7 +741,7 @@ 2, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2)), 1000, 10, ПоддельныеПотоки @@ -898,7 +898,7 @@ 1, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), 30000, 5000 ); @@ -1031,7 +1031,7 @@ 2, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия)), 1000, 10, ПоддельныеПотоки @@ -1056,37 +1056,6 @@ КонецФункции -// Собирает параметры коннектора для КоннекторУправляемоеОткрытие. -// -// Параметры: -// ПаузаОткрытия - Число - Пауза внутри открытия в миллисекундах. -// ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. -// ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. -// ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. -// ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. -// -// Возвращаемое значение: -// Массив - Параметры коннектора. -// -Функция ПараметрыТестовогоКоннектора( - Знач ПаузаОткрытия, - Знач ПадатьПриОткрытии, - Знач ПадатьПриЗавершенииТранзакции = Ложь, - Знач ПадатьПриЗакрытии = Ложь, - Знач ПланЗакрытий = Неопределено -) - - Параметры = Новый Массив; - Параметры.Добавить(ПаузаОткрытия); - Параметры.Добавить(ПадатьПриОткрытии); - Параметры.Добавить(ПадатьПриЗавершенииТранзакции); - Параметры.Добавить(ПадатьПриЗакрытии); - Параметры.Добавить(ПланЗакрытий); - - Возврат Параметры; - -КонецФункции - // Параметры вызова ЗафиксироватьТранзакциюПотокаИсполнения. // // Параметры: From 8d53e3b472ec7f116ae3fbd1cf9f3a91496e68cf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 13:51:44 +0000 Subject: [PATCH 40/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=B4=D0=BB=D0=B8=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B5=D0=B7=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit План закрытий вынесен в переменную: с префиксом модуля вызов помощника перестал умещаться в строку. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 8aff94b..3084706 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -1026,12 +1026,13 @@ Функция ПулСДвумяСвободнымиСоединениями(Знач НомерПадающегоЗакрытия) ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия); ПулДвухСоединений = Новый ПулСоединенийСБД( 2, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, ПланЗакрытий), 1000, 10, ПоддельныеПотоки From ad351124f6b7244ed15ff3a23a6c25ef65fc78bc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 14:18:23 +0000 Subject: [PATCH 41/55] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80?= =?UTF-8?q?=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81=D1=82=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=B2=D0=BE=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BE=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Признак закрытия читало только получение хранилища, поэтому закрытый менеджер продолжал принимать класс в модель, создавать по ней таблицы, открывать и завершать транзакцию потока и ходить в базу напрямую. Обещанная терминальность закрытия соблюдалась только для операций над сущностями. Проверка добавлена во все методы, кроме геттеров и повторного закрытия, а признак стал атомарным: его теперь читают вне блокировки набора хранилищ. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\276\321\201\321\202\320\265\320\271.md" | 3 +- ...20\276\321\201\321\202\320\265\320\271.os" | 26 +++++++--- ...20\260\320\275\320\275\321\213\321\205.os" | 50 +++++++++++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index fe25810..d3eefe2 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -132,7 +132,8 @@ ```bsl // Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер -// к работе непригоден. +// к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры +// и повторное закрытие, любой другой метод завершается ошибкой. // // Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает // ИсточникДанных.Закрыть(). diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 822b7ea..57f5817 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -3,6 +3,7 @@ #Использовать "../internal/concurrent" #Использовать asserts +#Использовать atomic #Использовать logos #Использовать reflector @@ -22,8 +23,9 @@ // попадание в набор читается под блокировкой чтения: сериализуется только создание. Перем БлокировкаХранилищ; -// Булево - Менеджер закрыт. Читается и пишется только при захваченной блокировке записи, поэтому -// хранилище, созданное после начала закрытия, в набор уже не попадет. +// АтомарноеБулево - Менеджер закрыт. Признак читают все методы менеджера, в том числе из других +// потоков и без блокировки набора хранилищ, поэтому атомарный. Выставляется под блокировкой записи: +// так хранилище, созданное после начала закрытия, в набор уже не попадет. Перем Закрыт; // Лог - Журнал библиотеки. @@ -58,7 +60,7 @@ МодельДанных = Новый МодельДанных; Хранилища = Новый Соответствие(); БлокировкаХранилищ = Новый БлокировкаЧтенияЗаписи(); - Закрыт = Ложь; + Закрыт = Новый АтомарноеБулево(Ложь); КонецПроцедуры @@ -68,6 +70,7 @@ // ТипСущности - Тип - Тип для добавления в модель // Процедура ДобавитьКлассВМодель(ТипСущности) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); ПроверитьЧтоКлассЯвляетсяСущностью(ТипСущности); МодельДанных.СоздатьОбъектМодели(ТипСущности); @@ -86,6 +89,8 @@ // Процедура Инициализировать() Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); @@ -230,7 +235,8 @@ КонецПроцедуры // Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер -// к работе непригоден. +// к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры +// и повторное закрытие, любой другой метод завершается ошибкой. // // Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает // ИсточникДанных.Закрыть(). @@ -241,7 +247,7 @@ Попытка - Закрыт = Истина; + Закрыт.Установить(Истина); Для Каждого КлючИЗначение Из Хранилища Цикл КлючИЗначение.Значение.Закрыть(); @@ -268,18 +274,21 @@ // попадают в эту транзакцию, а завершить ее другое фоновое задание не может. // Процедура НачатьТранзакцию() Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ЗафиксироватьТранзакцию() Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ОтменитьТранзакцию() Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры @@ -301,6 +310,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); РаботаСКоннекторами.ВыполнитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецПроцедуры @@ -323,6 +333,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецФункции @@ -391,10 +402,11 @@ // Проверяет, что менеджер не закрыт. // -// Вызывается при захваченной блокировке записи: признак закрытия ей и защищен. +// Закрытие терминально: работать закрытым менеджером нельзя, и каждый его метод, кроме геттеров +// и повторного закрытия, начинается с этой проверки. // Процедура ПроверитьЧтоМенеджерНеЗакрыт() - Ожидаем.Что(Закрыт, "Менеджер сущностей закрыт").ЭтоЛожь(); + Ожидаем.Что(Закрыт.Получить(), "Менеджер сущностей закрыт").ЭтоЛожь(); КонецПроцедуры // Проверяет, что тип сущности зарегистрирован в модели данных менеджера. diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 155b5f0..e62d028 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -219,6 +219,56 @@ КонецПроцедуры +// Закрытый менеджер не работает совсем, а не только по части хранилищ: иначе модель можно было бы +// набрать заново и создать по ней таблицы, а транзакция потока открылась бы такому менеджеру, +// который ни одной операции над данными в нее уже не пропустит. Работают только геттеры и повторное +// закрытие. +&Тест +Процедура ЗакрытыйМенеджерРаботатьОтказывается() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "работа после закрытия"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Менеджер.Закрыть(); + + ПараметрыКлассаВМодель = Новый Массив; + ПараметрыКлассаВМодель.Добавить(Тип("Автор")); + + ПараметрыАлгоритма = Новый Массив; + ПараметрыАлгоритма.Добавить("Коннектор -> Коннектор"); + + Ожидаем.Что(Менеджер, "Класс в модель закрытого менеджера не добавляется") + .Метод("ДобавитьКлассВМодель", ПараметрыКлассаВМодель) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Таблицы по модели закрытого менеджера не создаются") + .Метод("Инициализировать") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не начинается") + .Метод("НачатьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не фиксируется") + .Метод("ЗафиксироватьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не отменяется") + .Метод("ОтменитьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Прямой доступ к БД через закрытый менеджер закрыт") + .Метод("ВыполнитьСКоннектором", ПараметрыАлгоритма) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Вычисление коннектором через закрытый менеджер закрыто") + .Метод("ВычислитьСКоннектором", ПараметрыАлгоритма) + .ВыбрасываетИсключение("закрыт"); + + Источник.Закрыть(); + +КонецПроцедуры + // Хранилища принадлежат менеджеру, поэтому закрытие менеджера их и закрывает: прочитанные // сущности из пула сущностей уходят. &Тест From 97278891d60e4192683938490095ccd529f83189 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 14:44:57 +0000 Subject: [PATCH 42/55] =?UTF-8?q?=D0=98=D0=B7=20bsldoc=20=D1=83=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D1=8B=20=D0=BE=D1=82=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BA=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D1=83=20=D1=81=D0=BE=D1=81=D0=B5=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=D1=85=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Описание закрытия менеджера рассказывало, кто закрывает соединения, а описание транзакции хранилища - через что еще операции попадают в нее. Оба места говорят теперь только о своем классе. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.md" | 4 ++-- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.md" | 4 ++-- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.os" | 4 ++-- ...21\211\320\275\320\276\321\201\321\202\320\265\320\271.os" | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index d3eefe2..35668ee 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -135,8 +135,8 @@ // к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры // и повторное закрытие, любой другой метод завершается ошибкой. // -// Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает -// ИсточникДанных.Закрыть(). +// Закрывается только то, чем менеджер владеет: его хранилища и его модель данных. Соединения +// остаются открытыми. // Процедура Закрыть() ``` diff --git "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 21a1c30..19dcbca 100644 --- "a/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -103,8 +103,8 @@ ```bsl // Начинает транзакцию для текущего потока исполнения. // -// Транзакция принадлежит потоку исполнения: в нее попадают все его операции, в том числе через -// другие хранилища и через сам менеджер сущностей. +// Транзакция принадлежит потоку исполнения: в нее попадают все операции этого потока, а не только +// начатые через это хранилище. // Процедура НачатьТранзакцию() Экспорт ``` diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 57f5817..446acbb 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -238,8 +238,8 @@ // к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры // и повторное закрытие, любой другой метод завершается ошибкой. // -// Затрагивает только этот менеджер. Соединения продолжают жить: их закрывает -// ИсточникДанных.Закрыть(). +// Закрывается только то, чем менеджер владеет: его хранилища и его модель данных. Соединения +// остаются открытыми. // Процедура Закрыть() Экспорт diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 7a0053c..6ed8e5e 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -115,8 +115,8 @@ // Начинает транзакцию для текущего потока исполнения. // -// Транзакция принадлежит потоку исполнения: в нее попадают все его операции, в том числе через -// другие хранилища и через сам менеджер сущностей. +// Транзакция принадлежит потоку исполнения: в нее попадают все операции этого потока, а не только +// начатые через это хранилище. // Процедура НачатьТранзакцию() Экспорт Менеджер.НачатьТранзакцию(); From cc63fefd1da2484d186dbf95bb34b0ec439d6220 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 14:54:30 +0000 Subject: [PATCH 43/55] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=D0=B8=D1=82=20?= =?UTF-8?q?=D0=BE=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Соединения менеджеру не принадлежат, и его описанию о них говорить нечего. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" | 3 --- ...321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" | 3 --- 2 files changed, 6 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 35668ee..6dbad5c 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -135,9 +135,6 @@ // к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры // и повторное закрытие, любой другой метод завершается ошибкой. // -// Закрывается только то, чем менеджер владеет: его хранилища и его модель данных. Соединения -// остаются открытыми. -// Процедура Закрыть() ``` diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 446acbb..3fd73d4 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -238,9 +238,6 @@ // к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры // и повторное закрытие, любой другой метод завершается ошибкой. // -// Закрывается только то, чем менеджер владеет: его хранилища и его модель данных. Соединения -// остаются открытыми. -// Процедура Закрыть() Экспорт БлокировкаХранилищ.Запись().Заблокировать(); From d24a52968ff27ebda6ce44954219ef8cba3131fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 15:10:53 +0000 Subject: [PATCH 44/55] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=83=D0=BB=D0=B0=20=D0=BD=D0=B5=20=D1=81?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=20=D1=81=D0=BE=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BD=D1=8F=D1=82?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=B0=D0=B6=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Заявка, по которой поток исполнения уже забрал соединение, остается в очереди со ссылкой на него до ближайшей уборки очереди. Закрытие пула видело это соединение дважды: как соединение потока и как соединение заявки, - и дважды уменьшало счетчик занятых мест. Наблюдаемых последствий у этого нет: повторное закрытие коннектор игнорирует, а расхождение счетчика затирает обнуление в конце процедуры, до которого закрытие в этом состоянии всегда доходит. Правка защищает инвариант на будущее: снятая заявка в разборе пропускается. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\265\320\275\320\270\320\271\320\241\320\221\320\224.os" | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index ff536cf..a72a3b8 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -384,9 +384,13 @@ // Соединение, уже отданное заявке, не лежит ни за потоком, ни в свободных: заберет его // ожидающий, до которого очередь дошла. Без разбора заявок оно пережило бы закрытие пула. + // + // Соединение снятой заявки уже досталось своему потоку исполнения и закрыто вместе с + // остальными занятыми: сама заявка держится в очереди только до ближайшей ее уборки. Такую + // заявку закрытие пропускает - иначе одно соединение сосчиталось бы дважды. Пока Ожидающие.Количество() > 0 Цикл Заявка = Ожидающие.Подсмотреть().Получить(); - Если Заявка.Соединение <> Неопределено Тогда + Если НЕ Заявка.Снята И Заявка.Соединение <> Неопределено Тогда Заявка.Соединение.Закрыть(); Заявка.Соединение = Неопределено; СозданоСоединений = СозданоСоединений - 1; From d205d8074643822d15e245d636fb10f90349f701 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 16:09:49 +0000 Subject: [PATCH 45/55] =?UTF-8?q?=D0=98=D1=81=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=20=D1=81=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D1=8B=D0=BC=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=83=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Признак закрытия выставлялся до вызова в пул и откатывался при ошибке. Пока шло закрытие соединений, источник уже отвечал "закрыт", и второй закрывающий - явный вызов при остановке приложения или обработчик завершения потока - молча уходил, решив, что дело сделано; ошибку закрытия при этом получал только первый. Теперь признак выставляется после того, как пул закрыт, а пришедший вторым застает пул незакрытым и закрывает его сам: повторное закрытие пул переживает. Заодно три теста неудачного закрытия больше не оставляют свои пулы открытыми: план закрытий в них ограничен одним падением, чтобы пул можно было закрыть. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\260\320\275\320\275\321\213\321\205.os" | 20 ++++---- ...21\200\321\213\321\202\320\270\320\265.os" | 13 +++++ ...21\207\320\275\320\270\320\272\320\260.os" | 48 +++++++++++++++++++ ...20\270\320\273\320\270\321\202\321\213.os" | 6 ++- ...20\260\320\275\320\275\321\213\321\205.os" | 37 ++++++++++++++ ...20\270\320\271\320\241\320\221\320\224.os" | 12 ++++- 6 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 "tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index d498058..1a8aa90 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -211,19 +211,19 @@ // Процедура Закрыть() Экспорт - // Признак переключается атомарно: закрытие выполняется один раз. Своей блокировки поперек - // вызова в пул источник не держит: пул потокобезопасен сам, а вложенное ожидание вело бы - // к взаимной блокировке. - Если НЕ пЗакрыт.СравнитьИУстановить(Ложь, Истина) Тогда + Если пЗакрыт.Получить() Тогда Возврат; КонецЕсли; - Попытка - пПул.Закрыть(); - Исключение - пЗакрыт.Установить(Ложь); - ВызватьИсключение; - КонецПопытки; + // Признак выставляется после того, как пул закрыт, а не до: закрытие может упасть, и до + // удачного повтора источник закрытым не считается. Выстави признак раньше - и закрывающий, + // пришедший вторым, ушел бы молча, решив, что дело сделано, пока соединения еще живы. + // + // Второй закрывающий поэтому может застать пул незакрытым и закрыть его сам: повторное + // закрытие пул переживает. Своей блокировки поперек вызова в пул источник не держит: пул + // потокобезопасен сам, а вложенное ожидание вело бы к взаимной блокировке. + пПул.Закрыть(); + пЗакрыт.Установить(Истина); // Закрытые соединения и коннекторы больше никому не нужны: освобождаем их сразу, не дожидаясь // сборщика. diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index c3137de..0feb0e1 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -11,6 +11,8 @@ // [3] - Булево - Истина, если закрытие должно завершиться ошибкой. // [4] - ПланЗакрытийКоннектора - Необязательный план: с какого по счету закрытия в пуле // начинать падать. Задан - решает он, а не признак [3]. +// [5] - НаблюдательЗакрытияИсточника - Необязательный наблюдатель: его спрашивают в начале +// закрытия, то есть посреди закрытия источника. // // BSLLS:MagicNumber-off @@ -22,12 +24,15 @@ Перем ПадатьПриЗакрытии; // ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Неопределено, если не задан. Перем ПланЗакрытий; +// НаблюдательЗакрытияИсточника - Наблюдатель состояния источника. Неопределено, если не задан. +Перем НаблюдательЗакрытия; Процедура ПриСозданииОбъекта() Открыт = Ложь; ПадатьПриЗавершенииТранзакции = Ложь; ПадатьПриЗакрытии = Ложь; ПланЗакрытий = Неопределено; + НаблюдательЗакрытия = Неопределено; КонецПроцедуры // Открывает соединение так, как задано параметрами коннектора. @@ -53,6 +58,10 @@ ПланЗакрытий = ПараметрыКоннектора[4]; КонецЕсли; + Если ПараметрыКоннектора.Количество() > 5 Тогда + НаблюдательЗакрытия = ПараметрыКоннектора[5]; + КонецЕсли; + Открыт = Истина; КонецПроцедуры @@ -87,6 +96,10 @@ // Процедура Закрыть() Экспорт + Если НаблюдательЗакрытия <> Неопределено Тогда + НаблюдательЗакрытия.ЗаписатьСостояниеИсточника(); + КонецЕсли; + Если ПланЗакрытий <> Неопределено Тогда ПадаемСейчас = ПланЗакрытий.ЗакрытиеПадает(); Иначе diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" new file mode 100644 index 0000000..5ad1eb6 --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -0,0 +1,48 @@ +// Запоминает, считался ли источник закрытым в тот момент, когда закрывались его соединения. +// +// Источник закрывает пул, а пул - каждое свое соединение, поэтому коннектор и есть та точка, +// из которой видно середину закрытия. Наблюдателя коннектор получает параметрами коннектора, +// как и все остальные настройки КоннекторУправляемоеОткрытие. +// + +// ИсточникДанных - Источник, за закрытием которого наблюдаем. +Перем Источник; +// Булево - Ответ источника на вопрос "закрыт?", заданный посреди закрытия. +Перем ИсточникСчиталсяЗакрытым; + +Процедура ПриСозданииОбъекта() + Источник = Неопределено; + ИсточникСчиталсяЗакрытым = Неопределено; +КонецПроцедуры + +// Задает источник, за которым наблюдаем. Вызывается после создания источника: раньше его нет, +// а параметры коннектора собираются до. +// +// Параметры: +// ПИсточник - ИсточникДанных - Источник. +// +Процедура УстановитьИсточник(ПИсточник) Экспорт + Источник = ПИсточник; +КонецПроцедуры + +// Спрашивает у источника, закрыт ли он. Вызывается коннектором из его Закрыть(). +// +Процедура ЗаписатьСостояниеИсточника() Экспорт + + Если Источник = Неопределено Тогда + Возврат; + КонецЕсли; + + ИсточникСчиталсяЗакрытым = Источник.Закрыт(); + +КонецПроцедуры + +// Состояние источника, замеченное посреди закрытия. +// +// Возвращаемое значение: +// Булево - Истина, если источник уже считал себя закрытым, когда его соединения еще закрывались. +// Неопределено - Соединения не закрывались, спросить было некогда. +// +Функция ИсточникСчиталсяЗакрытым() Экспорт + Возврат ИсточникСчиталсяЗакрытым; +КонецФункции diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index 25df1c6..1f4a671 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -119,6 +119,8 @@ // ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. // ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. // ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. +// НаблюдательЗакрытия - НаблюдательЗакрытияИсточника - Наблюдатель, которого коннектор спрашивает +// в начале своего закрытия. // // Возвращаемое значение: // Массив - Параметры коннектора. @@ -128,7 +130,8 @@ Знач ПадатьПриОткрытии, Знач ПадатьПриЗавершенииТранзакции = Ложь, Знач ПадатьПриЗакрытии = Ложь, - Знач ПланЗакрытий = Неопределено + Знач ПланЗакрытий = Неопределено, + Знач НаблюдательЗакрытия = Неопределено ) Экспорт Параметры = Новый Массив; @@ -137,6 +140,7 @@ Параметры.Добавить(ПадатьПриЗавершенииТранзакции); Параметры.Добавить(ПадатьПриЗакрытии); Параметры.Добавить(ПланЗакрытий); + Параметры.Добавить(НаблюдательЗакрытия); Возврат Параметры; diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index e62d028..5db9330 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -219,6 +219,43 @@ КонецПроцедуры +// Источник считается закрытым только после того, как пул закрыт: до тех пор закрытие еще может +// упасть и его придется повторять. Выставь признак раньше - и второй закрывающий ушел бы молча, +// решив, что дело сделано, пока соединения еще живы. +&Тест +Процедура ИсточникСчитаетсяЗакрытымТолькоПослеЗакрытияПула() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), + "КоннекторУправляемоеОткрытие" + ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НаблюдательЗакрытияИсточника.os"), + "НаблюдательЗакрытияИсточника" + ); + + Наблюдатель = Новый НаблюдательЗакрытияИсточника; + Источник = Новый ИсточникДанных( + "Основной", + Тип("КоннекторУправляемоеОткрытие"), + "", + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Неопределено, Наблюдатель) + ); + Наблюдатель.УстановитьИсточник(Источник); + + // Соединение открывается, иначе закрывать в пуле будет нечего и наблюдателя никто не спросит. + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + + Ожидаем + .Что(Наблюдатель.ИсточникСчиталсяЗакрытым(), "Посреди закрытия источник закрытым еще не считается") + .ЭтоЛожь(); + Ожидаем.Что(Источник.Закрыт(), "После закрытия пула источник закрыт").ЭтоИстина(); + +КонецПроцедуры + // Закрытый менеджер не работает совсем, а не только по части хранилищ: иначе модель можно было бы // набрать заново и создать по ней таблицы, а транзакция потока открылась бы такому менеджеру, // который ни одной операции над данными в нее уже не пропустит. Работают только геттеры и повторное diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 3084706..d22b144 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -741,7 +741,7 @@ 2, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2)), + ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2, 1)), 1000, 10, ПоддельныеПотоки @@ -775,6 +775,8 @@ .Что(ПулПадающий.Занять().ПолучитьКоннектор().Открыт(), "Второму потоку досталось живое соединение") .ЭтоИстина(); + ПулПадающий.Закрыть(); + КонецПроцедуры // Свободное соединение, на котором закрытие споткнулось, из очереди свободных не выбывает: иначе @@ -798,6 +800,8 @@ .Что(ПулПадающий.КоличествоСвободных(), "Соединение, на котором упало закрытие, осталось в пуле") .Равно(1); + ПулПадающий.Закрыть(); + КонецПроцедуры // Место закрытого соединения возвращается пулу сразу, как соединение закрыто: счетчик занятых @@ -818,6 +822,8 @@ .Что(ПулПадающий.КоличествоСозданных(), "Место закрытого соединения освобождено") .Равно(1); + ПулПадающий.Закрыть(); + КонецПроцедуры // Закрытие пула терминально. Иначе ожидающий, проснувшийся после закрытия, молча открыл бы @@ -1026,7 +1032,9 @@ Функция ПулСДвумяСвободнымиСоединениями(Знач НомерПадающегоЗакрытия) ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); - ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия); + // Падает одно закрытие: тест закрывает пул в конце, и вечно падающий коннектор ему бы этого + // не дал. + ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия, 1); ПулДвухСоединений = Новый ПулСоединенийСБД( 2, From ed7923f4f12b37867571277ed085fee11ce3a7af Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 16:14:47 +0000 Subject: [PATCH 46/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D1=87=D0=B8=D1=81=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Наблюдатель закрытия нужен одному тесту, поэтому в общий помощник он не добавляется: тест дописывает его к собранным параметрам сам. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...321\202\320\270\320\273\320\270\321\202\321\213.os" | 9 +++------ ...320\224\320\260\320\275\320\275\321\213\321\205.os" | 10 ++++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index 1f4a671..b02b988 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -119,19 +119,17 @@ // ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. // ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. // ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. -// НаблюдательЗакрытия - НаблюдательЗакрытияИсточника - Наблюдатель, которого коннектор спрашивает -// в начале своего закрытия. // // Возвращаемое значение: -// Массив - Параметры коннектора. +// Массив - Параметры коннектора. Наблюдателя закрытия, если он нужен, вызывающий дописывает +// следующим элементом сам. // Функция ПараметрыТестовогоКоннектора( Знач ПаузаОткрытия, Знач ПадатьПриОткрытии, Знач ПадатьПриЗавершенииТранзакции = Ложь, Знач ПадатьПриЗакрытии = Ложь, - Знач ПланЗакрытий = Неопределено, - Знач НаблюдательЗакрытия = Неопределено + Знач ПланЗакрытий = Неопределено ) Экспорт Параметры = Новый Массив; @@ -140,7 +138,6 @@ Параметры.Добавить(ПадатьПриЗавершенииТранзакции); Параметры.Добавить(ПадатьПриЗакрытии); Параметры.Добавить(ПланЗакрытий); - Параметры.Добавить(НаблюдательЗакрытия); Возврат Параметры; diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 5db9330..71ae607 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -235,12 +235,10 @@ ); Наблюдатель = Новый НаблюдательЗакрытияИсточника; - Источник = Новый ИсточникДанных( - "Основной", - Тип("КоннекторУправляемоеОткрытие"), - "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Неопределено, Наблюдатель) - ); + ПараметрыКоннектора = ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь); + ПараметрыКоннектора.Добавить(Наблюдатель); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторУправляемоеОткрытие"), "", ПараметрыКоннектора); Наблюдатель.УстановитьИсточник(Источник); // Соединение открывается, иначе закрывать в пуле будет нечего и наблюдателя никто не спросит. From ae2947ca513aef79962bd50d43143e9777e1ccf6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 16:32:07 +0000 Subject: [PATCH 47/55] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BD=D0=BD=D0=B5=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=8E=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=BC,=20=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=D0=BC=D0=B8=20=D0=B2=20=D0=BC=D0=B0=D1=81=D1=81=D0=B8=D0=B2?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Поведение КоннекторУправляемоеОткрытие задавалось шестью значениями по местам в массиве параметров, и каждая новая настройка делала этот список длиннее и непонятнее. Теперь единственным элементом массива идет объект настроек с именованными полями, а помощник, собиравший позиционный список, удален. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...21\200\321\213\321\202\320\270\320\265.os" | 62 +++++-------------- ...20\272\321\202\320\276\321\200\320\260.os" | 47 ++++++++++++++ ...20\270\320\273\320\270\321\202\321\213.os" | 32 ---------- ...20\260\320\275\320\275\321\213\321\205.os" | 24 +++++-- ...20\270\320\271\320\241\320\221\320\224.os" | 44 ++++++++++--- 5 files changed, 119 insertions(+), 90 deletions(-) create mode 100644 "tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index 0feb0e1..163aeff 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -3,63 +3,35 @@ // Пул конструирует коннекторы сам, поэтому настроить экземпляр снаружи нельзя - настройки // приходят тем же путем, что и всегда, через параметры коннектора пула. // -// Параметры коннектора: -// [0] - Число - Пауза внутри открытия в миллисекундах. Изображает медленное подключение: -// рукопожатие, аутентификацию, недоступный хост. -// [1] - Булево - Истина, если открытие должно завершиться ошибкой. -// [2] - Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. -// [3] - Булево - Истина, если закрытие должно завершиться ошибкой. -// [4] - ПланЗакрытийКоннектора - Необязательный план: с какого по счету закрытия в пуле -// начинать падать. Задан - решает он, а не признак [3]. -// [5] - НаблюдательЗакрытияИсточника - Необязательный наблюдатель: его спрашивают в начале -// закрытия, то есть посреди закрытия источника. +// Единственный параметр коннектора - НастройкиТестовогоКоннектора, см. описание того модуля. // -// BSLLS:MagicNumber-off // Булево - Состояние соединения. Перем Открыт; -// Булево - Признак того, что завершение транзакции должно падать. -Перем ПадатьПриЗавершенииТранзакции; -// Булево - Признак того, что закрытие должно падать. -Перем ПадатьПриЗакрытии; -// ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Неопределено, если не задан. -Перем ПланЗакрытий; -// НаблюдательЗакрытияИсточника - Наблюдатель состояния источника. Неопределено, если не задан. -Перем НаблюдательЗакрытия; +// НастройкиТестовогоКоннектора - Настройки, с которыми соединение открыто. +Перем Настройки; Процедура ПриСозданииОбъекта() Открыт = Ложь; - ПадатьПриЗавершенииТранзакции = Ложь; - ПадатьПриЗакрытии = Ложь; - ПланЗакрытий = Неопределено; - НаблюдательЗакрытия = Неопределено; + Настройки = Неопределено; КонецПроцедуры -// Открывает соединение так, как задано параметрами коннектора. +// Открывает соединение так, как задано настройками. // // Параметры: // СтрокаСоединения - Строка - Не используется. -// ПараметрыКоннектора - Массив - Пауза и признак ошибки, см. описание модуля. +// ПараметрыКоннектора - Массив - Единственным элементом идут НастройкиТестовогоКоннектора. // Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт // BSLLS:UnusedParameters-off - Если ПараметрыКоннектора.Количество() > 0 И ПараметрыКоннектора[0] > 0 Тогда - Приостановить(ПараметрыКоннектора[0]); - КонецЕсли; - - Если ПараметрыКоннектора.Количество() > 1 И ПараметрыКоннектора[1] Тогда - ВызватьИсключение "Открытие соединения не удалось"; - КонецЕсли; - - ПадатьПриЗавершенииТранзакции = ПараметрыКоннектора.Количество() > 2 И ПараметрыКоннектора[2]; - ПадатьПриЗакрытии = ПараметрыКоннектора.Количество() > 3 И ПараметрыКоннектора[3]; + Настройки = ПараметрыКоннектора[0]; - Если ПараметрыКоннектора.Количество() > 4 Тогда - ПланЗакрытий = ПараметрыКоннектора[4]; + Если Настройки.ПаузаОткрытия > 0 Тогда + Приостановить(Настройки.ПаузаОткрытия); КонецЕсли; - Если ПараметрыКоннектора.Количество() > 5 Тогда - НаблюдательЗакрытия = ПараметрыКоннектора[5]; + Если Настройки.ПадатьПриОткрытии Тогда + ВызватьИсключение "Открытие соединения не удалось"; КонецЕсли; Открыт = Истина; @@ -87,7 +59,7 @@ // Общее поведение фиксации и отмены. // Процедура ЗавершитьТранзакцию() - Если ПадатьПриЗавершенииТранзакции Тогда + Если Настройки.ПадатьПриЗавершенииТранзакции Тогда ВызватьИсключение "Завершение транзакции не удалось"; КонецЕсли; КонецПроцедуры @@ -96,14 +68,14 @@ // Процедура Закрыть() Экспорт - Если НаблюдательЗакрытия <> Неопределено Тогда - НаблюдательЗакрытия.ЗаписатьСостояниеИсточника(); + Если Настройки.НаблюдательЗакрытия <> Неопределено Тогда + Настройки.НаблюдательЗакрытия.ЗаписатьСостояниеИсточника(); КонецЕсли; - Если ПланЗакрытий <> Неопределено Тогда - ПадаемСейчас = ПланЗакрытий.ЗакрытиеПадает(); + Если Настройки.ПланЗакрытий <> Неопределено Тогда + ПадаемСейчас = Настройки.ПланЗакрытий.ЗакрытиеПадает(); Иначе - ПадаемСейчас = ПадатьПриЗакрытии; + ПадаемСейчас = Настройки.ПадатьПриЗакрытии; КонецЕсли; Если ПадаемСейчас Тогда diff --git "a/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" new file mode 100644 index 0000000..65566fd --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -0,0 +1,47 @@ +// Настройки КоннекторУправляемоеОткрытие: что коннектор делает на открытии, завершении транзакции +// и закрытии. +// +// Пул конструирует коннекторы сам, поэтому настроить экземпляр снаружи нельзя - настройки приходят +// тем же путем, что и всегда, через параметры коннектора. Единственным элементом массива параметров +// идет этот объект: позиционный список из полудюжины значений читать невозможно. +// + +// Число - Пауза внутри открытия в миллисекундах. Изображает медленное подключение: рукопожатие, +// аутентификацию, недоступный хост. +Перем ПаузаОткрытия Экспорт; +// Булево - Истина, если открытие должно завершиться ошибкой. +Перем ПадатьПриОткрытии Экспорт; +// Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. +Перем ПадатьПриЗавершенииТранзакции Экспорт; +// Булево - Истина, если закрытие должно завершиться ошибкой. +Перем ПадатьПриЗакрытии Экспорт; +// ПланЗакрытийКоннектора - Общий на пул план падений закрытия: с какого по счету закрытия начинать +// падать. Задан - решает он, а не ПадатьПриЗакрытии. +Перем ПланЗакрытий Экспорт; +// НаблюдательЗакрытияИсточника - Наблюдатель, которого коннектор спрашивает в начале закрытия. +Перем НаблюдательЗакрытия Экспорт; + +Процедура ПриСозданииОбъекта() + + ПаузаОткрытия = 0; + ПадатьПриОткрытии = Ложь; + ПадатьПриЗавершенииТранзакции = Ложь; + ПадатьПриЗакрытии = Ложь; + ПланЗакрытий = Неопределено; + НаблюдательЗакрытия = Неопределено; + +КонецПроцедуры + +// Собирает параметры коннектора для ИсточникДанных и ПулСоединенийСБД. +// +// Возвращаемое значение: +// Массив - Параметры коннектора с этими настройками единственным элементом. +// +Функция ПараметрыКоннектора() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(ЭтотОбъект); + + Возврат Параметры; + +КонецФункции diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index b02b988..3e2b726 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -111,38 +111,6 @@ КонецФункции -// Собирает параметры коннектора для КоннекторУправляемоеОткрытие. -// -// Параметры: -// ПаузаОткрытия - Число - Пауза внутри открытия в миллисекундах. -// ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. -// ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. -// ПадатьПриЗакрытии - Булево - Истина, если закрытие соединения должно падать. -// ПланЗакрытий - ПланЗакрытийКоннектора - Общий на пул план падений закрытия. Задан - решает он. -// -// Возвращаемое значение: -// Массив - Параметры коннектора. Наблюдателя закрытия, если он нужен, вызывающий дописывает -// следующим элементом сам. -// -Функция ПараметрыТестовогоКоннектора( - Знач ПаузаОткрытия, - Знач ПадатьПриОткрытии, - Знач ПадатьПриЗавершенииТранзакции = Ложь, - Знач ПадатьПриЗакрытии = Ложь, - Знач ПланЗакрытий = Неопределено -) Экспорт - - Параметры = Новый Массив; - Параметры.Добавить(ПаузаОткрытия); - Параметры.Добавить(ПадатьПриОткрытии); - Параметры.Добавить(ПадатьПриЗавершенииТранзакции); - Параметры.Добавить(ПадатьПриЗакрытии); - Параметры.Добавить(ПланЗакрытий); - - Возврат Параметры; - -КонецФункции - // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 71ae607..91d2dac 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -138,6 +138,10 @@ ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), "КоннекторУправляемоеОткрытие" ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьДваИсточникаВДругомПотоке", Новый Массив, Истина); Задание.ОжидатьЗавершения(); @@ -233,12 +237,21 @@ ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НаблюдательЗакрытияИсточника.os"), "НаблюдательЗакрытияИсточника" ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); Наблюдатель = Новый НаблюдательЗакрытияИсточника; - ПараметрыКоннектора = ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь); - ПараметрыКоннектора.Добавить(Наблюдатель); + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.НаблюдательЗакрытия = Наблюдатель; - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторУправляемоеОткрытие"), "", ПараметрыКоннектора); + Источник = Новый ИсточникДанных( + "Основной", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора() + ); Наблюдатель.УстановитьИсточник(Источник); // Соединение открывается, иначе закрывать в пуле будет нечего и наблюдателя никто не спросит. @@ -417,11 +430,14 @@ // Процедура СоздатьДваИсточникаВДругомПотоке() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + ИсточникДругогоПотока = Новый ИсточникДанных( "Падающий при закрытии", Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Истина) + НастройкиКоннектора.ПараметрыКоннектора() ); МенеджерПадающего = Новый МенеджерСущностей(ИсточникДругогоПотока); МенеджерПадающего.ВычислитьСКоннектором("Коннектор -> Коннектор"); diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index d22b144..97dd799 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -42,6 +42,10 @@ ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "ПланЗакрытийКоннектора.os"), "ПланЗакрытийКоннектора" ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); ОпределитьТестовуюБазу(); @@ -515,11 +519,14 @@ &Тест Процедура ОткрытиеСоединенияНеДержитБлокировкуПула() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПаузаОткрытия = 3000; + ПулМедленный = Новый ПулСоединенийСБД( 2, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(3000, Ложь) + НастройкиКоннектора.ПараметрыКоннектора() ); Попытка @@ -592,11 +599,14 @@ &Тест Процедура ЗакрытиеВоВремяОткрытияНеОставляетСоединения() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПаузаОткрытия = 3000; + ПулМедленный = Новый ПулСоединенийСБД( 1, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(3000, Ложь) + НастройкиКоннектора.ПараметрыКоннектора() ); Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьМедленноеВФоне", Новый Массив, Истина); @@ -621,11 +631,14 @@ &Тест Процедура НеудачноеОткрытиеВозвращаетМестоВПул() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриОткрытии = Истина; + ПулПадающий = Новый ПулСоединенийСБД( 1, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Истина) + НастройкиКоннектора.ПараметрыКоннектора() ); Попытка @@ -654,11 +667,14 @@ &Тест Процедура ОшибкаЗавершенияТранзакцииНеОставляетСоединениеЗаПотокомИсполнения() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + ИсточникПадающий = Новый ИсточникДанных( "Падающий", Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Истина), + НастройкиКоннектора.ПараметрыКоннектора(), 1 ); ПулПадающий = ИсточникПадающий.ПулСоединений(); @@ -697,11 +713,14 @@ &Тест Процедура ОшибкаЗакрытияНеДелаетИсточникЗакрытым() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + ИсточникПадающий = Новый ИсточникДанных( "Падающий при закрытии", Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), + НастройкиКоннектора.ПараметрыКоннектора(), 1 ); ПулПадающий = ИсточникПадающий.ПулСоединений(); @@ -737,11 +756,14 @@ ПервыйПоток = Мок.Получить(Тип("ПотокИсполнения")); ВторойПоток = Мок.Получить(Тип("ПотокИсполнения")); + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(2, 1); + ПулПадающий = Новый ПулСоединенийСБД( 2, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(2, 1)), + НастройкиКоннектора.ПараметрыКоннектора(), 1000, 10, ПоддельныеПотоки @@ -900,11 +922,14 @@ &Тест Процедура НеудачноеЗакрытиеНеТеряетСоединениеЗаявки() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + ПулСОжиданием = Новый ПулСоединенийСБД( 1, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, Новый ПланЗакрытийКоннектора(1, 1)), + НастройкиКоннектора.ПараметрыКоннектора(), 30000, 5000 ); @@ -1034,13 +1059,14 @@ ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); // Падает одно закрытие: тест закрывает пул в конце, и вечно падающий коннектор ему бы этого // не дал. - ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия, 1); + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия, 1); ПулДвухСоединений = Новый ПулСоединенийСБД( 2, Тип("КоннекторУправляемоеОткрытие"), "", - ТестовыеУтилиты.ПараметрыТестовогоКоннектора(0, Ложь, Ложь, Ложь, ПланЗакрытий), + НастройкиКоннектора.ПараметрыКоннектора(), 1000, 10, ПоддельныеПотоки From 6f1b0d6d8c0c2068afa103b5977c8061573845ab Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 16:37:55 +0000 Subject: [PATCH 48/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=BE=D1=82=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D1=8B=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B0=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Настройки тестового коннектора хранят только данные, поэтому поля открыты, а диагностика снята в шапке - так же, как у ЭлементПорядка. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" | 3 +++ 1 file changed, 3 insertions(+) diff --git "a/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" index 65566fd..c208c37 100644 --- "a/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" +++ "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -5,6 +5,9 @@ // тем же путем, что и всегда, через параметры коннектора. Единственным элементом массива параметров // идет этот объект: позиционный список из полудюжины значений читать невозможно. // +// Класс хранит только данные, поэтому поля открыты - как у ЭлементПорядка. +// +// BSLLS:ExportVariables-off // Число - Пауза внутри открытия в миллисекундах. Изображает медленное подключение: рукопожатие, // аутентификацию, недоступный хост. From 4801fe26ec0ebbcc8895a196634fea9d7ce109ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 18:52:58 +0000 Subject: [PATCH 49/55] =?UTF-8?q?=D0=9D=D0=B5=D1=83=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=BD=D0=B5=D1=8F=D0=B2=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=B0=D1=82=20=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=BF=D1=80=D0=B8=D0=B3=D0=BE=D0=B4?= =?UTF-8?q?=D0=BD=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Явное завершение транзакции при ошибке помечало соединение непригодным, а откат, который делает сам пул при возврате соединения, ошибку просто глотал: соединение с неизвестным состоянием транзакции возвращалось в очередь свободных и доставалось следующему потоку исполнения. Теперь неудача любого завершения транзакции значит одно и то же. Заодно исправлен порядок в ВернутьВПул: возврат отмечается до проверки пригодности, иначе признак, выставленный неудачным откатом, был бы прочитан слишком рано. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 6 ++- ...20\270\320\265\320\241\320\221\320\224.os" | 7 ++-- ...20\270\320\271\320\241\320\221\320\224.os" | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index a72a3b8..548f12a 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -834,6 +834,10 @@ // Процедура ВернутьВПул(Соединение) + // Возврат откатывает транзакцию, которую поток исполнения не завершил сам, и о пригодности + // соединения до этого судить рано: неудачный откат делает соединение непригодным. + Соединение.ОтметитьВозврат(); + // Соединение, на котором не удалось завершить транзакцию, переиспользовать нельзя: в каком // состоянии осталась транзакция, неизвестно. Такое соединение закрывается, а его место // возвращается в пул - следующий поток исполнения откроет себе новое. @@ -843,8 +847,6 @@ Возврат; КонецЕсли; - Соединение.ОтметитьВозврат(); - Заявка = ПервыйОжидающий(); Если Заявка <> Неопределено Тогда Заявка.Соединение = Соединение; diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" index 52bd020..0b93c0d 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" @@ -130,8 +130,9 @@ // Отмечает возврат соединения в пул. // // Незавершенная транзакция при этом откатывается: соединение уходит следующему потоку, и -// оставлять на нем чужую начатую транзакцию нельзя. Ошибка отката подавляется - соединение -// возвращается в пул в любом случае, иначе оно было бы потеряно. +// оставлять на нем чужую начатую транзакцию нельзя. Ошибка отката наружу не идет - соединение +// возвращается в пул в любом случае, иначе оно было бы потеряно, - но соединение после нее +// непригодно так же, как после неудачного явного завершения транзакции. // Процедура ОтметитьВозврат() Экспорт @@ -139,7 +140,7 @@ Попытка ЭтотОбъект.ОтменитьТранзакцию(); Исключение - ВТранзакции = Ложь; + ИспортитьПослеНеудачногоЗавершения(); КонецПопытки; КонецЕсли; diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 97dd799..c5ea9b2 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -707,6 +707,44 @@ КонецПроцедуры +// Транзакцию, которую поток исполнения не завершил сам, откатывает пул при возврате соединения. +// Неудача такого отката значит ровно то же, что и неудача явной фиксации или отмены: в каком +// состоянии осталась транзакция на стороне СУБД, неизвестно, и переиспользовать соединение нельзя. +// +&Тест +Процедура НеудачныйНеявныйОткатДелаетСоединениеНепригодным() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулПадающий = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + ПулПадающий.Занять().НачатьТранзакцию(); + + // Поток исполнения оборвался, не завершив транзакцию: откат делает сам пул при возврате. + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); + + Ожидаем + .Что(ПулПадающий.КоличествоСвободных(), "Соединение с неоткатившейся транзакцией в очередь свободных не встало") + .Равно(0); + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место непригодного соединения вернулось в пул") + .Равно(0); + +КонецПроцедуры + // Ошибка при закрытии соединения не должна делать источник закрытым: иначе повторный вызов // ничего не сделает, и оставшиеся соединения некому закрыть. // From c82af24d237ffd39f67a1012448c2e1339c0f925 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 18:58:08 +0000 Subject: [PATCH 50/55] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Sonar:=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B2=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Диагностика Typo не знает слова "неоткатившейся"; по смыслу проверки речь о незавершенной транзакции. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index c5ea9b2..cf2cded 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -737,7 +737,7 @@ ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); Ожидаем - .Что(ПулПадающий.КоличествоСвободных(), "Соединение с неоткатившейся транзакцией в очередь свободных не встало") + .Что(ПулПадающий.КоличествоСвободных(), "Соединение с незавершенной транзакцией в очередь свободных не встало") .Равно(0); Ожидаем .Что(ПулПадающий.КоличествоСозданных(), "Место непригодного соединения вернулось в пул") From 44220bf00265445d22c010e58b12e0aed9ada624 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 19:05:06 +0000 Subject: [PATCH 51/55] =?UTF-8?q?=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=82=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BE=D1=82=D0=BD=D0=B8=D0=BC=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=B2=20=D0=BF=D1=83=D0=BB?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Счетчик занятых мест уменьшался после закрытия соединения, а оно к этому моменту уже отобрано у потока исполнения и пулу не видно. Упади закрытие - место оставалось занятым соединением, которого нет: при пределе в одно соединение пул переставал выдавать их вовсе. Теперь место возвращается до закрытия, в обеих ветках возврата. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\270\320\271\320\241\320\221\320\224.os" | 9 +++-- ...20\270\320\271\320\241\320\221\320\224.os" | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 548f12a..edd8cb4 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -841,9 +841,13 @@ // Соединение, на котором не удалось завершить транзакцию, переиспользовать нельзя: в каком // состоянии осталась транзакция, неизвестно. Такое соединение закрывается, а его место // возвращается в пул - следующий поток исполнения откроет себе новое. + // + // Место возвращается до закрытия: соединение уже отобрано у потока исполнения и пулу больше + // не видно, поэтому упади закрытие после уменьшения счетчика - место так и осталось бы + // занятым соединением, которого нет. Если Соединение.Испорчено() Тогда - Соединение.Закрыть(); СозданоСоединений = СозданоСоединений - 1; + Соединение.Закрыть(); Возврат; КонецЕсли; @@ -853,9 +857,10 @@ Возврат; КонецЕсли; + // Пул без предела соединений не держит: место возвращается так же, до закрытия. Если РазмерПула = 0 Тогда - Соединение.Закрыть(); СозданоСоединений = СозданоСоединений - 1; + Соединение.Закрыть(); Возврат; КонецЕсли; diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index cf2cded..2c002a0 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -707,6 +707,40 @@ КонецПроцедуры +// Место непригодного соединения возвращается пулу до его закрытия: соединение уже отобрано у +// потока исполнения и больше нигде не числится, поэтому упади закрытие - место осталось бы занятым +// навсегда, и пул перестал бы выдавать соединения вовсе. +// +&Тест +Процедура ОшибкаЗакрытияНепригодногоСоединенияНеТеряетМестоВПуле() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + + ИсточникПадающий = Новый ИсточникДанных( + "Падающий и при транзакции, и при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1 + ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); + + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ИсточникПадающий); + + // Фиксация не удалась - соединение непригодно, а его закрытие в пуле тоже падает. + Ожидаем + .Что(РаботаСКоннекторами, "Завершение транзакции завершилось ошибкой") + .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ИсточникПадающий)) + .ВыбрасываетИсключение(); + + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место непригодного соединения вернулось в пул") + .Равно(0); + +КонецПроцедуры + // Транзакцию, которую поток исполнения не завершил сам, откатывает пул при возврате соединения. // Неудача такого отката значит ровно то же, что и неудача явной фиксации или отмены: в каком // состоянии осталась транзакция на стороне СУБД, неизвестно, и переиспользовать соединение нельзя. From 9cbcdaa6ab53f1b6499b1617c69061f237ae1dbb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 05:38:07 +0000 Subject: [PATCH 52/55] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D1=87=D0=B8=D0=BA=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B2=20=D0=BF=D1=83=D0=BB=D0=B5=20=D0=BD=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=D0=B4=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D1=87=D0=B8=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Подписчиков у завершения потока столько, сколько пулов и источников его застали, и движок обходит их одним циклом, не перехватывая ошибки обработчиков. Ошибка освобождения ресурсов уходит в лог, а не наружу: иначе подписавшиеся позже своего вызова не дождутся. То же решение уже принято в ИсточникДанных. Заодно "ПотокИсполнения" слитно заменен на "Поток исполнения" в тексте ошибки ожидания и в комментариях к полям; цитата этой ошибки в docs/ПотокобезопаснаяРаботаСБД.md приведена к фактическому тексту. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...21\202\320\260\320\241\320\221\320\224.md" | 2 +- ...20\270\320\271\320\241\320\221\320\224.os" | 31 ++++++++--- ...20\270\320\271\320\241\320\221\320\224.os" | 51 +++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 281ab84..298a678 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -70,7 +70,7 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" следующий контекст встает в очередь; освобожденное соединение достается тому, кто ждет дольше всех. Ожидание в очереди ограничено по времени - по умолчанию 30 секунд. Не дождавшийся контекст -получает ошибку `Контекст не дождался свободного соединения за N мс`, а не висит бесконечно: +получает ошибку `Поток исполнения не дождался свободного соединения за N мс`, а не висит бесконечно: соединение мог не освободить контекст, который жив и продолжает работать, а у живого пул соединение не отбирает. diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index edd8cb4..f8e9caa 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -1,6 +1,7 @@ #Использовать "../concurrent" #Использовать collectionos +#Использовать logos // ПриоритетнаяОчередь - Очередь свободных соединений. Первым выдается возвращенное последним. // @@ -14,11 +15,11 @@ // Перем НомерПоследнейЗаявки; -// Соответствие - ПотокИсполнения исполнения и выданное ему соединение. +// Соответствие - Поток исполнения и выданное ему соединение. // Перем СоединенияПотоковИсполнения; -// Соответствие - ПотокИсполнения исполнения и сколько раз он занял соединение, не освободив. +// Соответствие - Поток исполнения и сколько раз он занял соединение, не освободив. // Перем ГлубиныЗахвата; @@ -34,7 +35,7 @@ // Перем СозданоСоединений; -// Соответствие - ПотокиИсполнения, которые прямо сейчас открывают себе соединение. Место в пуле +// Соответствие - Потоки исполнения, которые прямо сейчас открывают себе соединение. Место в пуле // за ними уже занято, а самого соединения еще нет: открытие идет вне блокировки. // Перем Брони; @@ -68,6 +69,11 @@ // Перем Потоки; +// Лог - Журнал пула: в него уходит ошибка освобождения ресурсов завершившегося потока исполнения, +// жаловаться о которой уже некому. +// +Перем Лог; + // Пул соединений с базой данных. // // Через пул идут все обращения к БД. Соединение выдается потоку исполнения на время операции @@ -124,6 +130,8 @@ ); КонецЕсли; + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.pool"); + РазмерПула = ПРазмерПула; ТипКоннектора = ПТипКоннектора; СтрокаСоединения = ПСтрокаСоединения; @@ -171,15 +179,24 @@ Блокировка.Запись().Заблокировать(); + // Подписчиков у завершения потока столько, сколько пулов и источников его застали, и движок + // обходит их одним циклом, не перехватывая ошибки обработчиков. Выпусти этот обработчик + // исключение, и до подписавшихся позже очередь не дойдет: их соединения так и остались бы + // за завершившимся потоком. Ошибка уходит в лог: поток кончился, и сообщать о ней некому. Попытка ОсвободитьРесурсыПотокаИсполнения(ПотокИсполнения); + ОшибкаОсвобождения = ""; Исключение - Блокировка.Запись().Разблокировать(); - ВызватьИсключение; + ОшибкаОсвобождения = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); КонецПопытки; Блокировка.Запись().Разблокировать(); + // Запись в лог - вне блокировки: держать ее ради журнала незачем. + Если НЕ ПустаяСтрока(ОшибкаОсвобождения) Тогда + Лог.Ошибка("Не удалось освободить ресурсы завершившегося потока исполнения: %1", ОшибкаОсвобождения); + КонецЕсли; + КонецПроцедуры // BSLLS:UnusedLocalMethod-on @@ -198,7 +215,7 @@ ПотокИсполнения = Потоки.Текущий(); - // ПотокИсполнения может не дойти до Освободить(): оборваться, забыть освободить или просто + // Поток исполнения может не дойти до Освободить(): оборваться, забыть освободить или просто // закончиться. Подписка снимает с пула обязанность выяснять это опросом - о завершении // сообщит сам поток исполнения. Повторная подписка тем же обработчиком ничего не добавляет. ДобавитьОбработчик ПотокИсполнения.ПриЗавершении, ПриЗавершенииПотокаИсполнения; @@ -710,7 +727,7 @@ Блокировка.Запись().Разблокировать(); ВызватьИсключение СтрШаблон( - "ПотокИсполнения не дождался свободного соединения за %1 мс", + "Поток исполнения не дождался свободного соединения за %1 мс", Формат(ПределОжидания, "ЧГ=") ); diff --git "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index 2c002a0..eda97f7 100644 --- "a/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/tests/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -27,6 +27,8 @@ Перем СбрасыватьТаблицы; // Соединение, удерживающее базу SQLite в памяти живой. Не задано для PostgreSQL. Перем ДержательБазыВПамяти; +// Признак того, что подписчик завершения потока, вставший в очередь после пула, был вызван. +Перем ПодписчикПослеПулаВызван; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -779,6 +781,55 @@ КонецПроцедуры +// Обработчик завершения потока исполнения, встающий в очередь подписчиков после пула. Используется +// тестом "ОшибкаОсвобожденияНеОбрываетОстальныхПодписчиковЗавершенияПотока". +// +Процедура ОтметитьВызовПодписчикаПослеПула() Экспорт + ПодписчикПослеПулаВызван = Истина; +КонецПроцедуры + +// Освобождение ресурсов завершившегося потока исполнения ошибку наружу не выпускает. Подписчиков +// у завершения потока столько, сколько пулов и источников его застали, и движок обходит их одним +// циклом, не перехватывая ошибки обработчиков: выпусти этот обработчик исключение - и до +// подписавшихся позже очередь не дойдет. +// +&Тест +Процедура ОшибкаОсвобожденияНеОбрываетОстальныхПодписчиковЗавершенияПотока() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулПадающий = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + + // Незавершенная транзакция делает соединение непригодным при возврате, а его закрытие падает: + // освобождение ресурсов потока исполнения натыкается на ошибку. + ПулПадающий.Занять().НачатьТранзакцию(); + + ПодписчикПослеПулаВызван = Ложь; + ДобавитьОбработчик ОборвавшийсяПоток.ПриЗавершении, ОтметитьВызовПодписчикаПослеПула; + + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); + + Ожидаем + .Что(ПодписчикПослеПулаВызван, "Подписчик, вставший в очередь после пула, дождался вызова") + .ЭтоИстина(); + +КонецПроцедуры + // Ошибка при закрытии соединения не должна делать источник закрытым: иначе повторный вызов // ничего не сделает, и оставшиеся соединения некому закрыть. // From c2d346218354bba8bbb9e0a92f1b6085bbeaf38d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 06:22:37 +0000 Subject: [PATCH 53/55] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B7=D0=BD=D0=B0?= =?UTF-8?q?=D0=BA=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80=D0=B0=20=D0=B2?= =?UTF-8?q?=D1=8B=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=D0=B8=D1=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Порядок в Закрыть() приведен к тому же, что и в ИсточникДанных: споткнись закрытие хранилищ на середине, менеджер закрытым не считается и закрытие можно повторить. Блокировка записи при этом не отпускается, поэтому хранилище, запрошенное во время закрытия, по-прежнему не создается. Сохранить, Получить, ПолучитьОдно и Удалить получили проверку закрытия. Раньше они отказывали закрытому менеджеру лишь косвенно - через пустой кэш хранилищ, - и контракт "у закрытого менеджера работают только геттеры" держался на устройстве кэша. Тест на отказ этих четырех методов добавлен в ЗакрытыйМенеджерРаботатьОтказывается. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...20\276\321\201\321\202\320\265\320\271.os" | 16 ++++++++++++-- ...20\260\320\275\320\275\321\213\321\205.os" | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 3fd73d4..4abb86c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -154,6 +154,8 @@ // Процедура Сохранить(Сущность) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); @@ -179,6 +181,8 @@ // Функция Получить(ТипСущности, Знач ОпцииПоиска = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); Если ОпцииПоиска = Неопределено Тогда @@ -207,6 +211,8 @@ // Функция ПолучитьОдно(ТипСущности, Знач ОпцииПоиска = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); Если ОпцииПоиска = Неопределено Тогда @@ -226,6 +232,8 @@ // Процедура Удалить(Сущность) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); @@ -244,13 +252,17 @@ Попытка - Закрыт.Установить(Истина); - Для Каждого КлючИЗначение Из Хранилища Цикл КлючИЗначение.Значение.Закрыть(); КонецЦикла; Хранилища.Очистить(); + // Признак выставляется после того, как хранилища закрыты, а не до: споткнись закрытие + // на середине, менеджер закрытым не считается и закрытие можно повторить. Блокировка + // записи при этом не отпускалась, поэтому хранилище, запрошенное во время закрытия, + // увидит уже выставленный признак и создано не будет. + Закрыт.Установить(Истина); + Исключение БлокировкаХранилищ.Запись().Разблокировать(); diff --git "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" index 91d2dac..2dc9661 100644 --- "a/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/tests/\320\237\321\203\320\273\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -313,6 +313,28 @@ .Метод("ВычислитьСКоннектором", ПараметрыАлгоритма) .ВыбрасываетИсключение("закрыт"); + ПараметрыСущности = Новый Массив; + ПараметрыСущности.Добавить(Новый Автор); + + ПараметрыТипаСущности = Новый Массив; + ПараметрыТипаСущности.Добавить(Тип("Автор")); + + Ожидаем.Что(Менеджер, "Сохранение через закрытый менеджер не идет") + .Метод("Сохранить", ПараметрыСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Выборка через закрытый менеджер не идет") + .Метод("Получить", ПараметрыТипаСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Выборка одной сущности через закрытый менеджер не идет") + .Метод("ПолучитьОдно", ПараметрыТипаСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Удаление через закрытый менеджер не идет") + .Метод("Удалить", ПараметрыСущности) + .ВыбрасываетИсключение("закрыт"); + Источник.Закрыть(); КонецПроцедуры From 565712f9b386825ab5428566d0b513705dd374b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 11:57:38 +0000 Subject: [PATCH 54/55] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=20=D1=87=D0=B8=D1=81=D1=82=D0=B8=D1=82=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Модель - метаданные типов, а не ресурс: соединений, файлов и памяти сверх собранной сборщиком она не держит и уходит в мусор вместе с менеджером. Очистка не освобождала ничего, зато была единственной записью в модель после инициализации - и гонялась с чтением модели в методах данных, которое идет без блокировки. Формулировка про освобождение модели убрана из bsldoc Закрыть() и из docs/МенеджерСущностей.md и docs/ПотокобезопаснаяРаботаСБД.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...11\320\275\320\276\321\201\321\202\320\265\320\271.md" | 6 +++--- ...61\320\276\321\202\320\260\320\241\320\221\320\224.md" | 3 +-- ...11\320\275\320\276\321\201\321\202\320\265\320\271.os" | 8 +++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 6dbad5c..2e37340 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -131,9 +131,9 @@ ### Закрыть ```bsl -// Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер -// к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры -// и повторное закрытие, любой другой метод завершается ошибкой. +// Закрывает хранилища менеджера: после Закрыть() менеджер к работе непригоден. Закрытие +// терминально - у закрытого менеджера работают только геттеры и повторное закрытие, любой +// другой метод завершается ошибкой. // Процедура Закрыть() ``` diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index 298a678..e8456ff 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -145,8 +145,7 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" `ИсточникДанных.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. Тот же эффект дает завершение потока, в котором источник создан, если автозакрытие не выключено. `МенеджерСущностей.Закрыть()` источник не трогает: менеджер его не создавал, а закрывает свои -хранилища и освобождает модель данных. Хранилища других менеджеров того же источника это не -затрагивает. +хранилища. Хранилища других менеджеров того же источника это не затрагивает. Закрытие терминально на обоих уровнях. Закрытый источник соединений не выдает: обращение через него завершается ошибкой `Источник данных <Имя> закрыт`. Закрытый пул тоже: ожидающий, diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 4abb86c..5c5d759 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -242,9 +242,9 @@ КонецПроцедуры -// Закрывает хранилища менеджера и освобождает его модель данных: после Закрыть() менеджер -// к работе непригоден. Закрытие терминально - у закрытого менеджера работают только геттеры -// и повторное закрытие, любой другой метод завершается ошибкой. +// Закрывает хранилища менеджера: после Закрыть() менеджер к работе непригоден. Закрытие +// терминально - у закрытого менеджера работают только геттеры и повторное закрытие, любой +// другой метод завершается ошибкой. // Процедура Закрыть() Экспорт @@ -273,8 +273,6 @@ БлокировкаХранилищ.Запись().Разблокировать(); - МодельДанных.Очистить(); - КонецПроцедуры // Начинает транзакцию для текущего потока исполнения. From 556fcc3e63773fc050b0450946b8eac7b36fea8b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 13:45:07 +0000 Subject: [PATCH 55/55] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=BD=D1=8B=D1=85.=D0=9E=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D1=82=D1=8C()=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После того как закрытие менеджера перестало чистить модель, метод остался без вызовов. Модель неизменяема с момента инициализации и живет столько же, сколько менеджер, - опустошать ее незачем и некому. Ломающее изменение публичного API, но версия уже поднята до 5.4.0.0 этим же PR. Метод убран и из docs/МодельДанных.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017UYvrFghKi7WmhoF3vvpyp --- ...214\320\224\320\260\320\275\320\275\321\213\321\205.md" | 7 ------- ...214\320\224\320\260\320\275\320\275\321\213\321\205.os" | 4 ---- 2 files changed, 11 deletions(-) diff --git "a/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" index a80411a..0d428dd 100644 --- "a/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -28,10 +28,3 @@ Функция СоздатьОбъектМодели(ТипСущности) Экспорт ``` -### Очистить - -```bsl - -Процедура Очистить() Экспорт -``` - diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" index 289d71b..538f3bb 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -23,7 +23,3 @@ Возврат ОбъектМодели; КонецФункции - -Процедура Очистить() Экспорт - ХранилищеМодели.Очистить(); -КонецПроцедуры