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 dd16e32..4a1818b 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" @@ -241,13 +241,14 @@ // @internal // Для служебного пользования. // -// Возвращает пул сущностей из хранилища сущностей, привязанного к переданному типу сущности. +// Возвращает пул сущностей текущего потока исполнения из хранилища сущностей, привязанного к +// переданному типу сущности. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели. // // Возвращаемое значение: -// Соответствие - Пул сущностей. +// Соответствие - Пул сущностей текущего потока. // Функция ПолучитьПулСущностей(ТипСущности) ``` 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 c2994f8..574b475 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" @@ -161,6 +161,38 @@ PostgreSQL это закрывает параметр строки соедин Прежний `ПолучитьКоннектор()` удален: он отдавал соединение наружу, не имея способа получить его обратно, поэтому соединение оставалось за контекстом навсегда. +### Экземпляры сущностей + +Прочитанные сущности хранилище запоминает в пуле сущностей, и у каждого контекста исполнения пул +свой. Внутри контекста строка читается одним и тем же экземпляром и между вызовами: повторное +чтение того же идентификатора заполняет поля этого экземпляра заново, а ссылки других сущностей +указывают на него же. Разные контексты получают разные экземпляры одной строки, поэтому чтение в +одном не стирает несохраненные правки другого и не смешивает снимки строки, прочитанные в разное +время. + +```bsl +// Основной поток +Автор = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), 1); +Автор.Имя = "Правка"; + +// Фоновое задание в это же время +АвторЗадания = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), 1); // Свой экземпляр, правку не трогает +``` + +Новый контекст создает прочитанные экземпляры заново. Это дешево: тип активной записи +регистрируется один раз на класс сущности, и экземпляр создается из него без компиляции модуля. + +Экземпляр можно передать в другой контекст, но там это обычный объект: сохранение запишет его +поля, а чтение того же идентификатора вернет экземпляр уже этого контекста. + +Сохранения пул не согласует. Если два контекста (или два процесса) изменили одну строку каждый в +своем экземпляре, в БД остается сохраненное последним: версию строки библиотека не проверяет. + +Пул контекста растет, пока контекст жив, - у основного потока долгоживущего процесса до закрытия +менеджера. `ХранилищеСущностей.Закрыть()` и `МенеджерСущностей.Закрыть()` отбрасывают прочитанные +экземпляры во всех контекстах: пул закрывающего освобождается сразу, остальных - при их следующем +обращении к хранилищу или по завершении контекста. + ### Закрытие `ИсточникДанных.Закрыть()` закрывает свободные соединения и соединения закрывающего контекста 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 19dcbca..f9d10bf 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,9 @@ ```bsl // Выполняет очистку текущего Хранилища сущностей. // -// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. +// Отбрасывает ранее прочитанные экземпляры во всех потоках исполнения: следующая операция читает +// сущность из БД в новый экземпляр. Пул текущего потока освобождается сразу, пулы других потоков - +// при их следующем обращении к хранилищу или по завершении потока. // Процедура Закрыть() Экспорт ``` @@ -179,10 +181,11 @@ // @internal // Для служебного пользования. // -// Возвращает пул сущностей текущего объекта. +// Возвращает пул сущностей текущего потока исполнения: прочитанные им экземпляры по +// идентификаторам. Пул, заведенный до очистки хранилища, отбрасывается и заводится заново. // // Возвращаемое значение: -// Соответствие - Пул сущностей. +// Соответствие - Пул сущностей текущего потока. // Функция ПолучитьПулСущностей() Экспорт ``` diff --git a/packagedef b/packagedef index 3252731..0f6e468 100644 --- a/packagedef +++ b/packagedef @@ -23,7 +23,7 @@ .ЗависитОт("fs", "1.2.0") .ЗависитОт("lambdas", "0.3.4") .ЗависитОт("logos", "1.7.1") - .ЗависитОт("decorator", "3.0.0") + .ЗависитОт("decorator", "3.1.1") .ЗависитОт("extends", "0.2.0") .ЗависитОт("reflector", "0.7.1") .ЗависитОт("sql", "1.3.3") 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 42ad700..768d8cf 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,12 +1,16 @@ #Использовать decorator +#Использовать reflector -Функция ТипСущности(Сущность) Экспорт - ТипСущности = ТипЗнч(Сущность); - Если ТипСущности = Тип("Сценарий") Тогда - ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность); - КонецЕсли; +// Соответствие - Тип сущности: имя зарегистрированного типа ее активной записи. +Перем ТипыЗаписей; + +// БлокировкаРесурса - Защищает ТипыЗаписей и регистрацию типов: модуль общий на процесс. +Перем Блокировка; + +Перем Рефлектор; - Возврат ТипСущности; +Функция ТипСущности(Сущность) Экспорт + Возврат ОбработкаДекоратора.ИсходныйТип(Сущность); КонецФункции // Создает экземпляр сущности, расширенный методами паттерна Active Record. @@ -15,6 +19,12 @@ // поэтому в методы записи его передавать не нужно, а операцию хранилище все равно делегирует // своему менеджеру. // +// Экземпляр создается из типа активной записи, зарегистрированного один раз на класс сущности: +// компилировать модуль на каждый экземпляр дорого, а экземпляры заново создает каждый поток +// исполнения. Тип общий у всех менеджеров процесса, поэтому хранилище и модель проставляются +// экземпляру после создания, а не значениями полей по умолчанию: те остались бы в реестре типов +// навсегда и держали бы хранилище первого менеджера. +// // Параметры: // ОбъектМодели - ОбъектМодели - Модель сущности, экземпляр которой создается. // ХранилищеСущностей - ХранилищеСущностей - Хранилище типа этой сущности. @@ -23,32 +33,86 @@ // Произвольный - Экземпляр класса сущности с методами Прочитать, Сохранить и Удалить. // Функция Создать(ОбъектМодели, ХранилищеСущностей) Экспорт - Сущность = Новый(ОбъектМодели.ТипСущности()); - Декоратор = Новый ПостроительДекоратора(Сущность) + Сущность = Новый(ТипЗаписи(ОбъектМодели.ТипСущности())); + + Рефлектор.УстановитьСвойство(Сущность, "_ХранилищеСущностей", ХранилищеСущностей); + Рефлектор.УстановитьСвойство(Сущность, "_ОбъектМодели", ОбъектМодели); + + Возврат Сущность; + +КонецФункции + +// Возвращает имя типа активной записи класса сущности, при первом обращении регистрируя его. +// +// Параметры: +// ТипСущности - Тип - Класс сущности. +// +// Возвращаемое значение: +// Строка - Имя зарегистрированного типа. +// +Функция ТипЗаписи(Знач ТипСущности) + + Блокировка.Заблокировать(); + + Попытка + + ИмяТипа = ТипыЗаписей.Получить(ТипСущности); + Если ИмяТипа = Неопределено Тогда + ИмяТипа = ЗарегистрироватьТипЗаписи(ТипСущности); + ТипыЗаписей.Вставить(ТипСущности, ИмяТипа); + КонецЕсли; + + Исключение + Ошибка = ИнформацияОбОшибке(); + Блокировка.Разблокировать(); + ВызватьИсключение; + КонецПопытки; + + Блокировка.Разблокировать(); + + Возврат ИмяТипа; + +КонецФункции + +// Регистрирует тип активной записи класса сущности под уникальным в процессе именем. +// +// Параметры: +// ТипСущности - Тип - Класс сущности. +// +// Возвращаемое значение: +// Строка - Имя зарегистрированного типа. +// +Функция ЗарегистрироватьТипЗаписи(Знач ТипСущности) + + ИмяТипа = "АктивнаяЗапись_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", ""); + + Построитель = Новый ПостроительДекоратора(ТипСущности) .Импорт(Новый Импорт("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 5cd1bc2..e3fcba5 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" @@ -22,7 +22,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // Сущность - Произвольный - Сохраняемая сущность. // Процедура Сохранить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт @@ -60,7 +60,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // ОпцииПоиска - Соответствие, ОпцииПоиска - Опции поиска. // // Возвращаемое значение: @@ -82,7 +82,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // ОпцииПоиска - ОпцииПоиска - Опции поиска в терминах колонок таблицы. // ИмяОперации - Строка - Имя операции для наблюдателей. // @@ -123,7 +123,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // ОпцииПоиска - Соответствие, ОпцииПоиска, Произвольный - Опции поиска; любой другой тип - идентификатор. // // Возвращаемое значение: @@ -157,7 +157,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // Сущность - Произвольный - Удаляемая сущность. // Процедура Удалить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт @@ -410,7 +410,7 @@ // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. // Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. // // Возвращаемое значение: @@ -503,7 +503,7 @@ // Параметры: // Менеджер - МенеджерСущностей - Менеджер, которому принадлежит хранилище сущности. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ПулСущностей - Соответствие - Экземпляры сущностей, прочитанные текущим потоком исполнения. // ЗначениеИдентификатора - Произвольный - Идентификатор сущности. // // Возвращаемое значение: @@ -511,20 +511,16 @@ // Функция ПолучитьСущностьИзПула(Менеджер, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) - Сущность = ПулСущностей.ПолучитьИлиУмолчание(ЗначениеИдентификатора, Неопределено); + Сущность = ПулСущностей.Получить(ЗначениеИдентификатора); Если Сущность <> Неопределено Тогда Возврат Сущность; КонецЕсли; ХранилищеСущностей = Менеджер.ПолучитьХранилищеСущностей(ОбъектМодели.ТипСущности()); Сущность = АктивнаяЗапись.Создать(ОбъектМодели, ХранилищеСущностей); + ПулСущностей.Вставить(ЗначениеИдентификатора, Сущность); - // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться - // один, иначе разные потоки получат разные объекты одной строки. Победителем остается тот, - // кто вставил первым, а свой лишний экземпляр вызывающий просто выбрасывает. - Предыдущая = ПулСущностей.ВставитьЕслиОтсутствует(ЗначениеИдентификатора, Сущность); - - Возврат ?(Предыдущая.Пустой(), Сущность, Предыдущая.Получить()); + Возврат Сущность; КонецФункции 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 8d5fe2f..c653a1c 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" @@ -457,13 +457,14 @@ // // Для служебного пользования. // -// Возвращает пул сущностей из хранилища сущностей, привязанного к переданному типу сущности. +// Возвращает пул сущностей текущего потока исполнения из хранилища сущностей, привязанного к +// переданному типу сущности. // // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели. // // Возвращаемое значение: -// Соответствие - Пул сущностей. +// Соответствие - Пул сущностей текущего потока. // Функция ПолучитьПулСущностей(ТипСущности) Экспорт Возврат ПолучитьХранилищеСущностей(ТипСущности).ПолучитьПулСущностей(); 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 6ed8e5e..9810eda 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" @@ -1,13 +1,22 @@ #Использовать "../internal" -#Использовать collectionos +#Использовать atomic // ОбъектМодели - Модель сущности, к типу которой привязано хранилище. Перем ОбъектМодели; -// СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей, ключ - идентификатор. -// Карта разделяется всеми потоками исполнения, поэтому синхронизированная. -Перем ПулСущностей; +// Строка - Ключ, под которым пул сущностей хранилища лежит в данных потока исполнения. +// +// Пул у каждого потока свой: внутри потока на идентификатор один экземпляр и между вызовами, а +// разные потоки получают разные экземпляры одной строки. Так чтение в одном потоке не +// перезаписывает поля экземпляра, с которым работает другой. Пул пишет и читает только сам поток, +// блокировка не нужна. +Перем КлючПула; + +// АтомарноеЧисло - Номер очистки хранилища. Закрыть увеличивает его: данные чужого потока +// вычистить нельзя, поэтому пул другого потока, заведенный до очистки, отбрасывается при его +// следующем обращении. +Перем НомерОчистки; // МенеджерСущностей - Менеджер, которому принадлежит хранилище: хранилище подставляет в операции // свой тип сущности и делегирует их ему. @@ -24,7 +33,8 @@ Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПМенеджер) ОбъектМодели = ПОбъектМодели; - ПулСущностей = Новый СинхронизированнаяКарта(Новый КартаСоответствие()); + КлючПула = "entity.entities." + Строка(Новый УникальныйИдентификатор); + НомерОчистки = Новый АтомарноеЧисло(0); Менеджер = ПМенеджер; КонецПроцедуры @@ -107,10 +117,13 @@ // Выполняет очистку текущего Хранилища сущностей. // -// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. +// Отбрасывает ранее прочитанные экземпляры во всех потоках исполнения: следующая операция читает +// сущность из БД в новый экземпляр. Пул текущего потока освобождается сразу, пулы других потоков - +// при их следующем обращении к хранилищу или по завершении потока. // Процедура Закрыть() Экспорт - ПулСущностей.Очистить(); + НомерОчистки.ИнкрементироватьИПолучить(); + ТекущийПоток().Данные.Удалить(КлючПула); КонецПроцедуры // Начинает транзакцию для текущего потока исполнения. @@ -190,11 +203,23 @@ // // Для служебного пользования. // -// Возвращает пул сущностей текущего объекта. +// Возвращает пул сущностей текущего потока исполнения: прочитанные им экземпляры по +// идентификаторам. Пул, заведенный до очистки хранилища, отбрасывается и заводится заново. // // Возвращаемое значение: -// СинхронизированнаяКарта - Пул сущностей. +// Соответствие - Пул сущностей текущего потока. // Функция ПолучитьПулСущностей() Экспорт - Возврат ПулСущностей; + + Данные = ТекущийПоток().Данные; + Номер = НомерОчистки.Получить(); + Пул = Данные.Получить(КлючПула); + + Если Пул = Неопределено ИЛИ Пул.НомерОчистки <> Номер Тогда + Пул = Новый ФиксированнаяСтруктура("НомерОчистки, Сущности", Номер, Новый Соответствие); + Данные.Вставить(КлючПула, Пул); + КонецЕсли; + + Возврат Пул.Сущности; + КонецФункции 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\264\320\265\321\200\320\266\320\272\320\270\320\247\321\202\320\265\320\275\320\270\321\217.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\264\320\265\321\200\320\266\320\272\320\270\320\247\321\202\320\265\320\275\320\270\321\217.os" new file mode 100644 index 0000000..ca209ee --- /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\264\320\265\321\200\320\266\320\272\320\270\320\247\321\202\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,53 @@ +// Наблюдатель, который задерживает помеченный поток перед чтением сущности заданного типа. +// +// Нужен тестам, где поток должен остановиться посреди сборки сущности: на вложенном чтении +// ссылки, когда часть полей уже заполнена, а часть еще нет. Помечает поток сам тест (фоновое +// задание), кладя признак в данные своего потока исполнения под ключом КлючПометки. Остальные +// потоки наблюдатель пропускает сразу. Дойдя до задержки, наблюдатель отмечает СигналДошел, а +// продолжает, когда тест отметит СигналОтпустить. +// + +// Тип - Тип сущности, перед чтением которой поток задерживается. +Перем ТипСущности; +// Строка - Ключ пометки задерживаемого потока. +Перем КлючПометки; +// СигналСобытия - Отмечается, когда помеченный поток дошел до задержки. +Перем СигналДошел; +// СигналСобытия - Отмечает тест, отпуская помеченный поток. +Перем СигналОтпустить; + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта(Знач ПТипСущности, Знач ПСигналДошел, Знач ПСигналОтпустить) + ТипСущности = ПТипСущности; + КлючПометки = "entity.tests.read-delay." + Строка(Новый УникальныйИдентификатор); + СигналДошел = ПСигналДошел; + СигналОтпустить = ПСигналОтпустить; +КонецПроцедуры + +// Ключ, под которым поток кладет в свои данные признак Истина, чтобы его задержали. +// +// Возвращаемое значение: +// Строка - Ключ. +// +Функция КлючПометки() Экспорт + Возврат КлючПометки; +КонецФункции + +Процедура ПередСобытием(Событие) Экспорт + + Если Событие.Вид() <> ВидыСобытийИсточникаДанных.Операция() + ИЛИ Событие.Операция() <> "ПолучитьОдно" + ИЛИ Событие.ТипСущности() <> ТипСущности Тогда + Возврат; + КонецЕсли; + Если ТекущийПоток().Данные.Получить(КлючПометки) <> Истина Тогда + Возврат; + КонецЕсли; + + СигналДошел.Отметить(); + СигналОтпустить.Дождаться(30000); + +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры 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 331aa60..40d4519 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" @@ -86,6 +86,53 @@ КонецПроцедуры +// Экземпляр сущности создается из типа активной записи, зарегистрированного один раз на класс, а не +// компиляцией своего модуля: компиляция стоила бы на каждый экземпляр, а экземпляры заново +// создает каждый поток исполнения. +&Тест +Процедура ЭкземплярыСоздаютсяИзЗарегистрированногоТипа() Экспорт + + Первый = Хранилище.СоздатьЭлемент(); + Второй = Хранилище.СоздатьЭлемент(); + + Ожидаем.Что(ТипЗнч(Первый), "Экземпляры одного типа").Равно(ТипЗнч(Второй)); + Ожидаем.Что(ТипЗнч(Первый) = Тип("Сценарий"), "Экземпляр не скомпилирован отдельно").ЭтоЛожь(); + Ожидаем.Что(ОбработкаДекоратора.ИсходныйТип(Первый), "Исходный тип - класс сущности").Равно(Тип("Автор")); + +КонецПроцедуры + +// Тип активной записи общий для всех менеджеров процесса, а хранилище у каждого свое: экземпляр, +// созданный хранилищем другого менеджера, сохраняется в базу этого менеджера. +&Тест +Процедура ЭкземплярДругогоМенеджераСохраняетсяЧерезСвоеХранилище() Экспорт + + ХранилищеВПамяти.Очистить("ХранилищеСущностейДругойМенеджер"); + ДругойИсточник = Новый ИсточникДанных("Другой", Тип("КоннекторInMemory"), "ХранилищеСущностейДругойМенеджер"); + ДругойИсточник.УстановитьАвтоЗакрытие(Ложь); + ДругойМенеджер = ТестовыеУтилиты.МенеджерАвторов(ДругойИсточник); + + Попытка + Автор = ДругойМенеджер.ПолучитьХранилищеСущностей(Тип("Автор")).СоздатьЭлемент(); + Автор.Имя = "Иван"; + Автор.Сохранить(); + + ВДругомМенеджере = ДругойМенеджер.Получить(Тип("Автор")).Количество(); + ВМенеджереТеста = Хранилище.Получить().Количество(); + Исключение + Ошибка = ИнформацияОбОшибке(); + ДругойМенеджер.Закрыть(); + ДругойИсточник.Закрыть(); + ВызватьИсключение; + КонецПопытки; + + ДругойМенеджер.Закрыть(); + ДругойИсточник.Закрыть(); + + Ожидаем.Что(ВДругомМенеджере, "Сущность сохранена в базу другого менеджера").Равно(1); + Ожидаем.Что(ВМенеджереТеста, "В базу менеджера теста сущность не попала").Равно(0); + +КонецПроцедуры + &Тест Процедура ПолучитьБезОпцийВозвращаетВсеСущности() Экспорт diff --git "a/tests/\320\255\320\272\320\267\320\265\320\274\320\277\320\273\321\217\321\200\321\213\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271\320\222\320\237\320\276\321\202\320\276\320\272\320\260\321\205.os" "b/tests/\320\255\320\272\320\267\320\265\320\274\320\277\320\273\321\217\321\200\321\213\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271\320\222\320\237\320\276\321\202\320\276\320\272\320\260\321\205.os" new file mode 100644 index 0000000..684fd11 --- /dev/null +++ "b/tests/\320\255\320\272\320\267\320\265\320\274\320\277\320\273\321\217\321\200\321\213\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271\320\222\320\237\320\276\321\202\320\276\320\272\320\260\321\205.os" @@ -0,0 +1,314 @@ +// Экземпляры прочитанных сущностей в разных потоках исполнения. +// +// Пул сущностей ведется на поток исполнения: внутри потока на идентификатор один экземпляр и между +// вызовами, а разные потоки получают разные экземпляры одной строки. Иначе чтение в одном потоке +// перезаписывало бы поля экземпляра, с которым работает другой: стирало несохраненные правки и +// смешивало снимки строки, прочитанные в разное время. +// +// BSLLS:MagicNumber-off +// BSLLS:LatinAndCyrillicSymbolInWord-off +// BSLLS:DuplicateStringLiteral-off + +#Использовать ".." +#Использовать "utils" + +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; +// МенеджерСущностей - Менеджер, который тесты создают заново перед каждым тестом. +Перем МенеджерСущностей; + +&ПередКаждым +Процедура ПередЗапускомТеста() Экспорт + + ХранилищеВПамяти.Очистить("ЭкземплярыСущностейВПотоках"); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СигналСобытия.os"), "СигналСобытия"); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НаблюдательЗадержкиЧтения.os"), + "НаблюдательЗадержкиЧтения" + ); + + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory"), "ЭкземплярыСущностейВПотоках"); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = ТестовыеУтилиты.МенеджерАвторов(Источник); + +КонецПроцедуры + +&ПослеКаждого +Процедура ПослеЗапускаТеста() Экспорт + МенеджерСущностей.Закрыть(); + МенеджерСущностей = Неопределено; + Источник.Закрыть(); +КонецПроцедуры + +// Поток правит прочитанный экземпляр и еще не сохранил его, а другой поток читает ту же строку. +// Правка не должна пропасть: чужое чтение заполняет свой экземпляр. +// +&Тест +Процедура ЧтениеВДругомПотокеНеЗатираетПравкуЭкземпляра() Экспорт + + Идентификатор = СохранитьАвтора("Исходное"); + + Автор = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), Идентификатор); + Автор.Имя = "Правка"; + + Задания = Новый Массив; + Задания.Добавить(ЗапуститьЗадание("ПрочитатьИмяАвтора", Идентификатор)); + ТестовыеУтилиты.ПроверитьЗадания(Задания, СрокЗаданий()); + + Ожидаем.Что(Задания[0].Результат, "Другой поток прочитал строку из БД").Равно("Исходное"); + Ожидаем.Что(Автор.Имя, "Несохраненная правка осталась").Равно("Правка"); + +КонецПроцедуры + +// В своем потоке строка читается одним и тем же экземпляром, а другой поток получает свой. +// +&Тест +Процедура ПотокиПолучаютРазныеЭкземплярыОднойСтроки() Экспорт + + Идентификатор = СохранитьАвтора("Исходное"); + + Автор = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), Идентификатор); + Ожидаем.Что(МенеджерСущностей.ПолучитьОдно(Тип("Автор"), Идентификатор) = Автор, "В потоке теста один экземпляр") + .ЭтоИстина(); + + Задания = Новый Массив; + Задания.Добавить(ЗапуститьЗадание("ПрочитатьАвтораДважды", Идентификатор)); + ТестовыеУтилиты.ПроверитьЗадания(Задания, СрокЗаданий()); + + Прочитанные = Задания[0].Результат; + Ожидаем.Что(Прочитанные[0] = Прочитанные[1], "В потоке задания один экземпляр").ЭтоИстина(); + Ожидаем.Что(Прочитанные[0] = Автор, "У потока задания свой экземпляр").ЭтоЛожь(); + +КонецПроцедуры + +// Задание собирает автора и останавливается на чтении его ссылки: часть полей уже заполнена из +// старого снимка строки. Тем временем строка меняется в БД, и тест читает ее заново. Задание +// должно получить свой снимок целиком, а не смесь со снимком теста. +// +&Тест +Процедура ЧтениеПосредиСборкиВДругомПотокеНеСмешиваетСнимки() Экспорт + + Идентификатор = СохранитьАвтора("Старое"); + + Дошел = Новый СигналСобытия; + Отпустить = Новый СигналСобытия; + Наблюдатель = Новый НаблюдательЗадержкиЧтения(Тип("СущностьБезГенерируемогоИдентификатора"), Дошел, Отпустить); + Источник.ДобавитьНаблюдателя(Наблюдатель); + + Задания = Новый Массив; + Попытка + + Задания.Добавить(ЗапуститьЗадание("ПрочитатьИмяАвтораСЗадержкой", Идентификатор, Наблюдатель.КлючПометки())); + Ожидаем.Что(Дошел.Дождаться(), "Задание остановилось на чтении ссылки").ЭтоИстина(); + + ЗаписатьИмяАвтораМимоМенеджера(Идентификатор, "Новое"); + Прочитанный = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), Идентификатор); + Ожидаем.Что(Прочитанный.Имя, "Тест прочитал новый снимок").Равно("Новое"); + + Отпустить.Отметить(); + + Исключение + Ошибка = ИнформацияОбОшибке(); + Отпустить.Отметить(); + ТестовыеУтилиты.СообщитьОЗаданиях(ТестовыеУтилиты.ДождатьсяЗаданий(Задания, СрокЗаданий())); + ВызватьИсключение; + КонецПопытки; + + ТестовыеУтилиты.ПроверитьЗадания(Задания, СрокЗаданий()); + + Ожидаем.Что(Задания[0].Результат, "Задание получило свой снимок строки").Равно("Старое"); + +КонецПроцедуры + +// Очистка хранилища в одном потоке отбрасывает и экземпляры, прочитанные другими потоками до нее: +// их следующее чтение берет строку из БД в новый экземпляр. +// +&Тест +Процедура ОчисткаХранилищаОтбрасываетЭкземплярыДругихПотоков() Экспорт + + Идентификатор = СохранитьАвтора("Исходное"); + + Прочитал = Новый СигналСобытия; + Очищено = Новый СигналСобытия; + + Параметры = Новый Массив; + Параметры.Добавить(МенеджерСущностей); + Параметры.Добавить(Идентификатор); + Параметры.Добавить(Прочитал); + Параметры.Добавить(Очищено); + Задания = Новый Массив; + Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПрочитатьАвтораДоИПослеОчистки", Параметры, Истина)); + + Попытка + Ожидаем.Что(Прочитал.Дождаться(), "Задание прочитало автора").ЭтоИстина(); + МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("Автор")).Закрыть(); + Очищено.Отметить(); + Исключение + Ошибка = ИнформацияОбОшибке(); + Очищено.Отметить(); + ТестовыеУтилиты.СообщитьОЗаданиях(ТестовыеУтилиты.ДождатьсяЗаданий(Задания, СрокЗаданий())); + ВызватьИсключение; + КонецПопытки; + + ТестовыеУтилиты.ПроверитьЗадания(Задания, СрокЗаданий()); + + Прочитанные = Задания[0].Результат; + Ожидаем.Что(Прочитанные[0] = Прочитанные[1], "После очистки задание получило новый экземпляр").ЭтоЛожь(); + +КонецПроцедуры + +// Читает автора и возвращает его имя. Запускается в фоновом задании. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер теста. +// Идентификатор - Число - Идентификатор автора. +// +// Возвращаемое значение: +// Строка - Имя прочитанного автора. +// +Функция ПрочитатьИмяАвтора(Менеджер, Идентификатор) Экспорт + Возврат Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор).Имя; +КонецФункции + +// Читает автора два раза. Запускается в фоновом задании. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер теста. +// Идентификатор - Число - Идентификатор автора. +// +// Возвращаемое значение: +// Массив - Экземпляры первого и второго чтения. +// +Функция ПрочитатьАвтораДважды(Менеджер, Идентификатор) Экспорт + + Прочитанные = Новый Массив; + Прочитанные.Добавить(Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор)); + Прочитанные.Добавить(Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор)); + + Возврат Прочитанные; + +КонецФункции + +// Помечает свой поток для наблюдателя задержки, читает автора и возвращает его имя. Запускается в +// фоновом задании. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер теста. +// Идентификатор - Число - Идентификатор автора. +// КлючПометки - Строка - Ключ пометки потока для наблюдателя. +// +// Возвращаемое значение: +// Строка - Имя прочитанного автора. +// +Функция ПрочитатьИмяАвтораСЗадержкой(Менеджер, Идентификатор, КлючПометки) Экспорт + ТекущийПоток().Данные.Вставить(КлючПометки, Истина); + Возврат Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор).Имя; +КонецФункции + +// Читает автора, ждет очистки хранилища тестом и читает снова. Запускается в фоновом задании. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер теста. +// Идентификатор - Число - Идентификатор автора. +// Прочитал - СигналСобытия - Отмечается после первого чтения. +// Очищено - СигналСобытия - Отмечает тест после очистки хранилища. +// +// Возвращаемое значение: +// Массив - Экземпляры чтений до и после очистки. +// +Функция ПрочитатьАвтораДоИПослеОчистки(Менеджер, Идентификатор, Прочитал, Очищено) Экспорт + + Прочитанные = Новый Массив; + Прочитанные.Добавить(Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор)); + Прочитал.Отметить(); + + Если НЕ Очищено.Дождаться() Тогда + ВызватьИсключение "Тест не очистил хранилище за 30 с"; + КонецЕсли; + + Прочитанные.Добавить(Менеджер.ПолучитьОдно(Тип("Автор"), Идентификатор)); + + Возврат Прочитанные; + +КонецФункции + +// Сохраняет автора со ссылкой на внешнюю сущность. +// +// Параметры: +// Имя - Строка - Имя автора. +// +// Возвращаемое значение: +// Число - Идентификатор сохраненного автора. +// +Функция СохранитьАвтора(Знач Имя) + + ВнешняяСущность = Новый СущностьБезГенерируемогоИдентификатора; + ВнешняяСущность.ВнутреннийИдентификатор = 7; + МенеджерСущностей.Сохранить(ВнешняяСущность); + + Автор = Новый Автор; + Автор.Имя = Имя; + Автор.ВтороеИмя = "Иванов"; + Автор.ВнешняяСущность = ВнешняяСущность; + МенеджерСущностей.Сохранить(Автор); + + Возврат Автор.ВнутреннийИдентификатор; + +КонецФункции + +// Меняет имя автора в БД, не трогая экземпляры менеджера: строка пишется коннектором напрямую. +// +// Параметры: +// Идентификатор - Число - Идентификатор автора. +// Имя - Строка - Новое имя. +// +Процедура ЗаписатьИмяАвтораМимоМенеджера(Знач Идентификатор, Знач Имя) + + ВнешняяСущность = Новый СущностьБезГенерируемогоИдентификатора; + ВнешняяСущность.ВнутреннийИдентификатор = 7; + + Автор = Новый Автор; + Автор.ВнутреннийИдентификатор = Идентификатор; + Автор.Имя = Имя; + Автор.ВтороеИмя = "Иванов"; + Автор.ВнешняяСущность = ВнешняяСущность; + + Контекст = Новый Структура; + Контекст.Вставить("ОбъектМодели", МенеджерСущностей.ПолучитьМодельДанных().Получить(Тип("Автор"))); + Контекст.Вставить("Автор", Автор); + МенеджерСущностей.ВыполнитьСКоннектором("Коннектор -> Коннектор.Сохранить(ОбъектМодели, Автор)", Контекст); + +КонецПроцедуры + +// Запускает фоновое задание, которому передаются менеджер теста и параметры метода. +// +// Параметры: +// ИмяМетода - Строка - Метод, выполняемый заданием. +// Идентификатор - Число - Идентификатор автора. +// Дополнительный - Произвольный - Третий параметр метода; Неопределено - метод принимает два. +// +// Возвращаемое значение: +// ФоновоеЗадание - Запущенное задание. +// +Функция ЗапуститьЗадание(Знач ИмяМетода, Знач Идентификатор, Знач Дополнительный = Неопределено) + + Параметры = Новый Массив; + Параметры.Добавить(МенеджерСущностей); + Параметры.Добавить(Идентификатор); + Если Дополнительный <> Неопределено Тогда + Параметры.Добавить(Дополнительный); + КонецЕсли; + + Возврат ФоновыеЗадания.Выполнить(ЭтотОбъект, ИмяМетода, Параметры, Истина); + +КонецФункции + +// Общий срок ожидания заданий теста. +// +// Возвращаемое значение: +// Число - Срок в миллисекундах. +// +Функция СрокЗаданий() + Возврат 30000; +КонецФункции