Skip to content

fix(entities): пул сущностей на поток исполнения - #156

Open
sfaqer wants to merge 5 commits into
nixel2007:masterfrom
sfaqer:claude/thread-safety-v-entity-pool
Open

sfaqer wants to merge 5 commits into
nixel2007:masterfrom
sfaqer:claude/thread-safety-v-entity-pool

Conversation

@sfaqer

@sfaqer sfaqer commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Требует decorator 3.1.1 (nixel2007/decorator#34, «Потокобезопасная регистрация типов»). Он выпущен, вместе с ним приходит зависимость osparser 0.1.0. В ветку влит текущий master (lambdas 0.3.5 из #157).

Что чинит

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

Дефект Как проявлялся на master Исправление
Чтение в одном потоке перезаписывало поля экземпляра, с которым работает другой Поток правил прочитанный экземпляр и ещё не сохранил его, другой поток читал ту же строку — правка пропадала Пул сущностей у каждого потока свой
Разные потоки получали один экземпляр строки Экземпляр, прочитанный в фоновом задании, был тем же объектом, что и в основном потоке Тот же пул на поток: внутри потока экземпляр один, у другого потока свой
Экземпляр попадал в пул до заполнения, и другой поток дозаполнял его своим снимком строки Задание, остановленное на чтении ссылки посреди сборки автора, получало имя из снимка, который другой поток прочитал позже Чужой поток больше не видит экземпляр, который собирается

Как теперь

  • Пул — Соответствие в ТекущийПоток().Данные под ключом хранилища. Пишет и читает его только свой поток, поэтому блокировки нет.
  • Внутри потока на идентификатор один экземпляр и между вызовами, как раньше. Повторное чтение того же идентификатора заполняет поля этого экземпляра заново.
  • ХранилищеСущностей.Закрыть() и МенеджерСущностей.Закрыть() отбрасывают экземпляры всех потоков. Пул своего потока освобождается сразу, пулы других — при их следующем обращении к хранилищу (по номеру очистки, АтомарноеЧисло) или по завершении потока.

Цена экземпляров в новом потоке

Общий пул на master кешировал не данные — каждое чтение и так идёт в БД, — а собранные экземпляры. АктивнаяЗапись строила декоратор компиляцией модуля (ЗагрузитьСценарийИзСтроки) на каждый экземпляр, около 1,8 мс. С пулом на поток каждый новый поток (фоновое задание, запрос веб-сервера) платил бы эту цену заново за каждую прочитанную сущность.

Поэтому второй коммит переводит АктивнаяЗапись на тип декоратора, зарегистрированный один раз на класс сущности (ЗарегистрироватьВСистемеТипов). Экземпляр создаётся из него через Новый.

  • Регистрация и соответствие «класс → тип» — под блокировкой модуля.
  • Тип общий у всех менеджеров процесса, поэтому _ХранилищеСущностей и _ОбъектМодели проставляются экземпляру после создания. Значения по умолчанию остались бы в реестре типов decorator навсегда и держали бы хранилище первого менеджера.
  • АктивнаяЗапись.ТипСущности — через ОбработкаДекоратора.ИсходныйТип: ТипЗнч экземпляра теперь зарегистрированный тип, а не Сценарий.

Замер, InMemory, Получить 200 авторов:

экземпляров нет в пуле (новый поток) экземпляры в пуле
компиляция на экземпляр 285–359 мс 11–15 мс
зарегистрированный тип 31–57 мс 12–26 мс

Создание экземпляра: 1,7–1,9 мс → 0,13–0,145 мс; регистрация типа — 10–20 мс один раз на класс.

Что меняется снаружи

  • Разные потоки исполнения (фоновые задания, запросы веб-сервера) получают разные экземпляры одной строки.
  • Служебный ПолучитьПулСущностей() отдаёт Соответствие текущего потока вместо общей СинхронизированнаяКарта.
  • Пул долгоживущего потока растёт до закрытия менеджера.
  • ТипЗнч экземпляра активной записи — зарегистрированный тип АктивнаяЗапись_<uuid>, а не Сценарий. Исходный класс по-прежнему отдаёт ОбработкаДекоратора.ИсходныйТип.
  • Зависимость decorator поднята до 3.1.1.
  • Потерянное обновление это не лечит: из двух сохранений одной строки остаётся последнее. Оптимистическая блокировка (колонка версии) — отдельная задача. Описано в новом разделе «Экземпляры сущностей» в docs/ПотокобезопаснаяРаботаСБД.md.

Тесты

Новый набор ЭкземплярыСущностейВПотоках (InMemory):

  • ЧтениеВДругомПотокеНеЗатираетПравкуЭкземпляра;
  • ПотокиПолучаютРазныеЭкземплярыОднойСтроки;
  • ЧтениеПосредиСборкиВДругомПотокеНеСмешиваетСнимки — новая фикстура НаблюдательЗадержкиЧтения держит задание на вложенном чтении ссылки;
  • страховочный ОчисткаХранилищаОтбрасываетЭкземплярыДругихПотоков: зелёный и на master, мутант «без номера очистки» тест поймал.

Первые три на master красные.

В ХранилищеСущностей:

  • ЭкземплярыСоздаютсяИзЗарегистрированногоТипа — красный до второго коммита;
  • страховочный ЭкземплярДругогоМенеджераСохраняетсяЧерезСвоеХранилище: тип общий у менеджеров, а экземпляр сохраняется через своё хранилище. Мутант «служебные поля — значения по умолчанию первой регистрации» тест поймал, а с ним ещё пять тестов набора.

Полный прогон:

  • с decorator 3.0.0: OneScript 2.2.0 — 321 из 321 (дважды), 2.3.0-next+a02323a1 — 321 из 321 (дважды), 2.2.0 только PostgreSQL — 304 из 304;
  • с decorator из Потокобезопасная регистрация типов decorator#34 (c2ff803, вместе с osparser 0.1.0): 2.2.0 — 321 из 321 (дважды), 2.3.0-next+a02323a1 — 321 из 321 (дважды), 2.2.0 только PostgreSQL — 304 из 304;
  • с выпущенным decorator 3.1.1 из хаба и lambdas 0.3.5, после слияния с master, без PostgreSQL: 2.2.0 — 305 из 305, 2.3.0-next+a02323a1 — 305 из 305.

На 2.2.0 остаётся гонка движка, которая была и на master: первое параллельное Новый одного типа портит кэш фабрик типов (EvilBeaver/OneScript#1762, в 2.3.0-next исправлено). Этот PR её не чинит.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Документация
    • Уточнено, что пул сущностей содержит экземпляры текущего потока исполнения.
    • Описано повторное использование экземпляров в рамках контекста, а также их поведение при чтении, сохранении и очистке хранилища.
  • Улучшения
    • В разных контекстах используются отдельные экземпляры сущностей. При передаче объекта в другой контекст сохраняются его поля, а чтение того же идентификатора возвращает экземпляр этого контекста.
    • При очистке хранилища пул текущего потока освобождается сразу, а пулы остальных потоков — при следующем обращении или завершении потока. При следующем чтении создается новый экземпляр; при параллельных сохранениях в базе остается последняя запись.

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

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

Потерянное обновление при двух сохранениях одной строки это не лечит:
остается сохраненное последним, это описано в документации.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 766abcd6-4f72-4863-80da-d0d1006305d5
📥 Commits

Reviewing files that changed from the base of the PR and between 5b0f24a and 4994b74.

📒 Files selected for processing (1)
  • src/internal/Модули/АктивнаяЗапись.os

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

Изменения регистрируют типы активных записей и переводят пул сущностей на хранение в данных текущего потока. При очистке хранилища пулы других потоков заменяются при следующем обращении. Тесты и документация описывают изоляцию экземпляров и очистку пулов.

Changes

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

Layer / File(s) Summary
Регистрация типов активных записей
src/internal/Модули/АктивнаяЗапись.os, tests/ХранилищеСущностей.os, packagedef
Тип активной записи регистрируется и кэшируется для класса сущности. Создание экземпляра задаёт хранилище и модель через рефлектор. Тесты проверяют исходный тип сущности и запись через хранилище другого менеджера. Минимальная версия decorator изменена с 3.0.0 на 3.1.1.
Пулы сущностей по потокам
src/Классы/ХранилищеСущностей.os, src/internal/Модули/РаботаСКоннекторами.os, src/Классы/МенеджерСущностей.os, docs/МенеджерСущностей.md, docs/ХранилищеСущностей.md, docs/ПотокобезопаснаяРаботаСБД.md
Хранилище получает пул из данных текущего потока и заменяет его при изменении номера очистки. Коннектор возвращает имеющийся экземпляр либо создаёт и вставляет новый. Документация описывает изоляцию пулов, передачу объектов между контекстами, очистку и отсутствие согласования изменений между пулами.
Проверки экземпляров в потоках
tests/fixtures/НаблюдательЗадержкиЧтения.os, tests/ЭкземплярыСущностейВПотоках.os
Тесты проверяют повторное использование экземпляра в потоке, разделение экземпляров между потоками, чтение при изменении данных и получение нового экземпляра после очистки. Наблюдатель задерживает выбранное чтение до сигнала освобождения.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Thread as Поток исполнения
  participant Storage as ХранилищеСущностей
  participant Connector as РаботаСКоннекторами
  participant ActiveRecord as АктивнаяЗапись
  Thread->>Storage: Запросить пул текущего потока
  Storage-->>Thread: Вернуть соответствие экземпляров
  Thread->>Connector: Прочитать сущность по идентификатору
  Connector->>Connector: Найти экземпляр в пуле
  alt Экземпляр отсутствует
    Connector->>ActiveRecord: Создать экземпляр сущности
    ActiveRecord-->>Connector: Вернуть экземпляр
    Connector->>Connector: Вставить экземпляр в пул
  end
  Connector-->>Thread: Вернуть экземпляр сущности
Loading

Suggested reviewers: nixel2007

Merge Risk: ⚪ Minimal · up to 4994b

This change separates entity pools by thread. The reviewed concerns do not establish a new material regression, so the PR is ready for normal pre-merge checks.

Architecture Summary

Architecture risk: 🔵 Low · up to 622f3

The change affects 4 systems.

Changed systems: src, docs, tests, packagedef

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 4 changed files map to changed impact.
  • observed — docs (service) was modified; 3 changed files map to changed impact.
  • observed — tests (service) was modified; 3 changed files map to changed impact.
  • observed — packagedef (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in docs/МенеджерСущностей.md: В описании ПолучитьПулСущностей уточнено, что пул берётся для текущего потока исполнения; в описании возвращаемого значения также указано, что это пул текущего потока.
  • observed — Modified behavior in docs/ПотокобезопаснаяРаботаСБД.md: Добавлен раздел о пуле сущностей, изолированном для каждого контекста: повторное чтение идентификатора обновляет прежний экземпляр, а разные контексты получают отдельные экземпляры одной строки.
  • observed — Modified behavior in docs/ПотокобезопаснаяРаботаСБД.md: Добавлено описание повторного создания экземпляров в новом контексте и передачи объекта между контекстами: сохранение записывает его поля, но чтение идентификатора возвращает экземпляр принимающего контекста.
  • observed — Modified behavior in docs/ПотокобезопаснаяРаботаСБД.md: Указано, что пул не согласует изменения: при сохранении одной строки из нескольких контекстов или процессов остается последняя запись, проверка версии строки не выполняется.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Заголовок кратко и точно описывает основное изменение: пул сущностей разделён по потокам исполнения.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🧪 Generate unit tests (beta)
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Кролик хранит сущность в потоке,
В каждом потоке — свой экземпляр.
Сбросит хранилище — будет замена,
Новый запрос принесёт его вновь.
Тихо тесты проверят работу,
И скачет довольный ушастый ревьюер.

Comment @coderabbitai help to get the list of available commands.

sfaqer and others added 2 commits October 4, 2026 20:20
…го один раз

Пул сущностей на поток исполнения создает экземпляры заново в каждом потоке, а
АктивнаяЗапись строила декоратор компиляцией модуля на каждый экземпляр (около
1,8 мс). Теперь тип активной записи регистрируется через
ЗарегистрироватьВСистемеТипов один раз на класс сущности, и экземпляр
создается из него через Новый (около 0,14 мс). Чтение 200 сущностей в новом
потоке - 31-57 мс вместо 285-359.

Тип общий у всех менеджеров процесса, поэтому хранилище и модель экземпляр
получает после создания: значения по умолчанию остались бы в реестре типов
decorator навсегда и держали бы хранилище первого менеджера. Тип сущности
экземпляра определяется через ОбработкаДекоратора.ИсходныйТип: ТипЗнч у него
теперь зарегистрированный тип, а не Сценарий.

Нужна decorator 3.1.1 с потокобезопасной регистрацией типов
(nixel2007/decorator#34).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@@ -1,12 +1,16 @@
#Использовать decorator
#Использовать reflector

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.

объект Рефлектор не из библиотеки reflector

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.

Да, Рефлектор встроен в движок, а reflector даёт только РефлекторОбъекта и ИнтерфейсОбъекта. Убрал #Использовать reflector в 4994b74.

Заодно влил свежий master (collectionos 0.8.4, oneunit 0.5.2). Без PostgreSQL на 2.2.0 и 2.3.0-next — 305 из 305.

sfaqer and others added 2 commits October 7, 2026 08:41
Рефлектор - встроенный тип движка, библиотека reflector дает только
РефлекторОбъекта и ИнтерфейсОбъекта.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants