Skip to content

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

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

sfaqer wants to merge 7 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, «Потокобезопасная регистрация типов») и collectionos 0.9.0 — обе версии уже в master, он влит в ветку.

Что чинит

Экземпляры прочитанных сущностей. Пул сущностей хранилища был один на все потоки: потоки получали один и тот же экземпляр строки и без согласования заполняли его поля. Для каждого дефекта сначала написан тест, красный на 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>, а не Сценарий. Исходный класс по-прежнему отдаёт ОбработкаДекоратора.ИсходныйТип.
  • Потерянное обновление это не лечит: из двух сохранений одной строки остаётся последнее. Оптимистическая блокировка (колонка версии) — отдельная задача. Описано в новом разделе «Экземпляры сущностей» в 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;
  • с реестром типов на КартаКопированиеПриЗаписи, после слияния с master (collectionos 0.9.0), без PostgreSQL: 2.2.0 — 312 из 312, 2.3.0-next+a02323a1 — 312 из 312.

На 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 →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: 4f08183f-4aa4-48f2-a3eb-0584a8024e9f


📥 Commits

Reviewing files that changed from the base of the PR and between 4994b74 and 784bf15.



📒 Files selected for processing (3)
  • docs/МенеджерСущностей.md
  • src/internal/Модули/АктивнаяЗапись.os
  • src/Классы/МенеджерСущностей.os


🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Классы/МенеджерСущностей.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
Добавлена регистрация типа записи для типа сущности. Создание записи использует зарегистрированный тип и устанавливает хранилище и модель. Тесты проверяют исходный тип и сохранение записи через другое хранилище.
Пулы сущностей по потокам
src/Классы/ХранилищеСущностей.os, src/internal/Модули/РаботаСКоннекторами.os, src/Классы/МенеджерСущностей.os, docs/ХранилищеСущностей.md, docs/МенеджерСущностей.md, docs/ПотокобезопаснаяРаботаСБД.md
Хранилище получает пул из данных текущего потока и заменяет его после очистки. Коннектор получает экземпляр из пула или создаёт и вставляет новый. Документация описывает изоляцию экземпляров, передачу объектов между контекстами и очистку пулов.
Проверки экземпляров в потоках
tests/ЭкземплярыСущностейВПотоках.os, tests/fixtures/НаблюдательЗадержкиЧтения.os
Добавлены тесты повторного использования экземпляров, чтения в разных потоках, чтения при изменении строки и повторного чтения после очистки. Наблюдатель задерживает выбранную операцию чтения для заданного потока.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Поток
  participant ХранилищеСущностей
  participant ДанныеПотока
  participant РаботаСКоннекторами
  Поток->>ХранилищеСущностей: ПолучитьПулСущностей
  ХранилищеСущностей->>ДанныеПотока: Получить пул и проверить номер очистки
  ДанныеПотока-->>ХранилищеСущностей: Пул текущего потока
  ХранилищеСущностей-->>РаботаСКоннекторами: Передать пул
  РаботаСКоннекторами->>РаботаСКоннекторами: Получить экземпляр по идентификатору
  РаботаСКоннекторами->>РаботаСКоннекторами: При отсутствии создать и вставить экземпляр
Loading

Suggested reviewers: nixel2007

Merge Risk: ⚪ Minimal · up to 784bf

The reviewed changes preserve the documented entity-instance and pool-cleanup behavior. No actionable merge-blocking issue was established.

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 | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed Заголовок кратко и точно описывает основное изменение: пул сущностей выделяется для каждого потока исполнения.

✨ 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>
Comment thread src/internal/Модули/АктивнаяЗапись.os Outdated
sfaqer and others added 2 commits October 7, 2026 08:41
Рефлектор - встроенный тип движка, библиотека reflector дает только
РефлекторОбъекта и ИнтерфейсОбъекта.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность);
КонецЕсли;
// Соответствие - Тип сущности: имя зарегистрированного типа ее активной записи.
Перем ТипыЗаписей;

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 разных типов.

sfaqer and others added 2 commits October 10, 2026 13:30
…риЗаписи

Тип записи класса берется из снимка карты без блокировки, а при первом
обращении регистрируется через ВычислитьЕслиОтсутствует ровно один раз,
даже если первые экземпляры создают несколько потоков сразу.

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