Skip to content
Open
5 changes: 3 additions & 2 deletions docs/МенеджерСущностей.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,14 @@
// @internal
// Для служебного пользования.
//
// Возвращает пул сущностей из хранилища сущностей, привязанного к переданному типу сущности.
// Возвращает пул сущностей текущего потока исполнения из хранилища сущностей, привязанного к
// переданному типу сущности.
//
// Параметры:
// ТипСущности - Тип - Тип сущности, зарегистрированный в Модели.
//
// Возвращаемое значение:
// Соответствие - Пул сущностей.
// Соответствие - Пул сущностей текущего потока.
//
Функция ПолучитьПулСущностей(ТипСущности)
```
Expand Down
32 changes: 32 additions & 0 deletions docs/ПотокобезопаснаяРаботаСБД.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,38 @@ PostgreSQL это закрывает параметр строки соедин
Прежний `ПолучитьКоннектор()` удален: он отдавал соединение наружу, не имея способа получить его
обратно, поэтому соединение оставалось за контекстом навсегда.

### Экземпляры сущностей

Прочитанные сущности хранилище запоминает в пуле сущностей, и у каждого контекста исполнения пул
свой. Внутри контекста строка читается одним и тем же экземпляром и между вызовами: повторное
чтение того же идентификатора заполняет поля этого экземпляра заново, а ссылки других сущностей
указывают на него же. Разные контексты получают разные экземпляры одной строки, поэтому чтение в
одном не стирает несохраненные правки другого и не смешивает снимки строки, прочитанные в разное
время.

```bsl
// Основной поток
Автор = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), 1);
Автор.Имя = "Правка";

// Фоновое задание в это же время
АвторЗадания = МенеджерСущностей.ПолучитьОдно(Тип("Автор"), 1); // Свой экземпляр, правку не трогает
```

Новый контекст создает прочитанные экземпляры заново. Это дешево: тип активной записи
регистрируется один раз на класс сущности, и экземпляр создается из него без компиляции модуля.

Экземпляр можно передать в другой контекст, но там это обычный объект: сохранение запишет его
поля, а чтение того же идентификатора вернет экземпляр уже этого контекста.

Сохранения пул не согласует. Если два контекста (или два процесса) изменили одну строку каждый в
своем экземпляре, в БД остается сохраненное последним: версию строки библиотека не проверяет.

Пул контекста растет, пока контекст жив, - у основного потока долгоживущего процесса до закрытия
менеджера. `ХранилищеСущностей.Закрыть()` и `МенеджерСущностей.Закрыть()` отбрасывают прочитанные
экземпляры во всех контекстах: пул закрывающего освобождается сразу, остальных - при их следующем
обращении к хранилищу или по завершении контекста.

### Закрытие

`ИсточникДанных.Закрыть()` закрывает свободные соединения и соединения закрывающего контекста
Expand Down
9 changes: 6 additions & 3 deletions docs/ХранилищеСущностей.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@
```bsl
// Выполняет очистку текущего Хранилища сущностей.
//
// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново.
// Отбрасывает ранее прочитанные экземпляры во всех потоках исполнения: следующая операция читает
// сущность из БД в новый экземпляр. Пул текущего потока освобождается сразу, пулы других потоков -
// при их следующем обращении к хранилищу или по завершении потока.
//
Процедура Закрыть() Экспорт
```
Expand Down Expand Up @@ -179,10 +181,11 @@
// @internal
// Для служебного пользования.
//
// Возвращает пул сущностей текущего объекта.
// Возвращает пул сущностей текущего потока исполнения: прочитанные им экземпляры по
// идентификаторам. Пул, заведенный до очистки хранилища, отбрасывается и заводится заново.
//
// Возвращаемое значение:
// Соответствие - Пул сущностей.
// Соответствие - Пул сущностей текущего потока.
//
Функция ПолучитьПулСущностей() Экспорт
```
Expand Down
68 changes: 52 additions & 16 deletions src/internal/Модули/АктивнаяЗапись.os
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#Использовать collectionos
#Использовать decorator

Функция ТипСущности(Сущность) Экспорт
ТипСущности = ТипЗнч(Сущность);
Если ТипСущности = Тип("Сценарий") Тогда
ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность);
КонецЕсли;
// КартаКопированиеПриЗаписи - Тип сущности: имя зарегистрированного типа ее активной записи.
// Модуль общий на процесс: карта читается без блокировки, а тип класса регистрируется один раз.
Перем ТипыЗаписей;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoW-карта из collectionos 0.9.0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, перевёл в 784bf15. ТипыЗаписей — КартаКопированиеПриЗаписи, своя БлокировкаРесурса и ТипЗаписи ушли: Создать берёт тип через ВычислитьЕслиОтсутствует. Тип читается из снимка без блокировки, а регистрируется ровно один раз, даже если первые экземпляры класса создают несколько потоков сразу. ВставитьЕслиОтсутствует здесь не подошёл бы: проигравшая регистрация оставила бы лишний тип в системе типов.

Регистрацию вызывает Действие, а оно видит только экспортные методы, поэтому ЗарегистрироватьТипЗаписи стала экспортной (модуль в src/internal).

Влил master (collectionos 0.9.0 из #155). Без PostgreSQL на 2.2.0 и 2.3.0-next — 312 из 312. Пробник: 8 фоновых заданий одновременно впервые создают экземпляр нового класса — во всех один тип (5 запусков на 2.2.0, 5 на next); мутант «прочитать, зарегистрировать, вставить» без атомарности даёт 8 разных типов.


// Действие - Регистрирует тип активной записи класса, которого еще нет в ТипыЗаписей.
Перем РегистрацияТипа;

Перем Рефлектор;

Возврат ТипСущности;
Функция ТипСущности(Сущность) Экспорт
Возврат ОбработкаДекоратора.ИсходныйТип(Сущность);
КонецФункции

// Создает экземпляр сущности, расширенный методами паттерна Active Record.
Expand All @@ -15,6 +20,12 @@
// поэтому в методы записи его передавать не нужно, а операцию хранилище все равно делегирует
// своему менеджеру.
//
// Экземпляр создается из типа активной записи, зарегистрированного один раз на класс сущности:
// компилировать модуль на каждый экземпляр дорого, а экземпляры заново создает каждый поток
// исполнения. Тип общий у всех менеджеров процесса, поэтому хранилище и модель проставляются
// экземпляру после создания, а не значениями полей по умолчанию: те остались бы в реестре типов
// навсегда и держали бы хранилище первого менеджера.
//
// Параметры:
// ОбъектМодели - ОбъектМодели - Модель сущности, экземпляр которой создается.
// ХранилищеСущностей - ХранилищеСущностей - Хранилище типа этой сущности.
Expand All @@ -23,32 +34,57 @@
// Произвольный - Экземпляр класса сущности с методами Прочитать, Сохранить и Удалить.
//
Функция Создать(ОбъектМодели, ХранилищеСущностей) Экспорт
Сущность = Новый(ОбъектМодели.ТипСущности());

Декоратор = Новый ПостроительДекоратора(Сущность)
ИмяТипа = ТипыЗаписей.ВычислитьЕслиОтсутствует(ОбъектМодели.ТипСущности(), РегистрацияТипа).Получить();
Сущность = Новый(ИмяТипа);

Рефлектор.УстановитьСвойство(Сущность, "_ХранилищеСущностей", ХранилищеСущностей);
Рефлектор.УстановитьСвойство(Сущность, "_ОбъектМодели", ОбъектМодели);

Возврат Сущность;

КонецФункции

// Регистрирует тип активной записи класса сущности под уникальным в процессе именем.
//
// Экспортная, потому что ее вызывает делегат РегистрацияТипа.
//
// Параметры:
// ТипСущности - Тип - Класс сущности.
//
// Возвращаемое значение:
// Строка - Имя зарегистрированного типа.
//
Функция ЗарегистрироватьТипЗаписи(Знач ТипСущности) Экспорт

ИмяТипа = "АктивнаяЗапись_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");

Построитель = Новый ПостроительДекоратора(ТипСущности)
.Импорт(Новый Импорт("decorator"))

.Поле(Новый Поле("_ХранилищеСущностей")
.ЗначениеПоУмолчанию(ХранилищеСущностей))
.Поле(Новый Поле("_ХранилищеСущностей"))

.Поле(Новый Поле("_ОбъектМодели")
.ЗначениеПоУмолчанию(ОбъектМодели))
.Поле(Новый Поле("_ОбъектМодели"))

.Метод(Новый Метод("Прочитать")
.Публичный()
.ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект));
|ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);"))


.Метод(Новый Метод("Сохранить")
.Публичный()
.ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);"))

.Метод(Новый Метод("Удалить")
.Публичный()
.ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);"))
.ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);"));

.Построить();
Построитель.ЗарегистрироватьВСистемеТипов(ИмяТипа);

Возврат ИмяТипа;

Возврат Декоратор;
КонецФункции

ТипыЗаписей = Новый КартаКопированиеПриЗаписи();
РегистрацияТипа = Новый Действие(ЭтотОбъект, "ЗарегистрироватьТипЗаписи");
Рефлектор = Новый Рефлектор;
Loading
Loading