Skip to content

fix(model): модель и описания колонок без гонок между потоками - #155

Open
sfaqer wants to merge 3 commits into
nixel2007:masterfrom
sfaqer:claude/thread-safety-b-model
Open

sfaqer wants to merge 3 commits into
nixel2007:masterfrom
sfaqer:claude/thread-safety-b-model

Conversation

@sfaqer

@sfaqer sfaqer commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Что чинит

Общие описания модели, которые читают все потоки, работающие с менеджером. Для каждого пункта сначала написан тест, красный на master, потом исправление.

Дефект Как проявлялся на master Исправление
МодельДанных хранила объекты модели в обычном Соответствие без блокировки, а классы можно регистрировать и после передачи менеджера другим потокам Задание, обходящее модель (как это делает Инициализировать), падало с «Collection was modified», пока другой поток регистрировал классы КартаКопированиеПриЗаписи (collectionos 0.9.0): модель меняется только при регистрации класса, а чтение и обход идут по неизменяемому снимку без блокировки
Повторная регистрация типа подменяла объект модели Хранилище, созданное раньше, и новые операции работали с разными объектами модели одного типа ВставитьЕслиОтсутствует: для известного типа возвращается существующий объект
Экспортный мутатор Служебный_ИмяТаблицы Любой код мог переименовать таблицу уже опубликованного объекта модели Имя таблицы — третий необязательный параметр конструктора ОбъектМодели, мутатор удалён. Имена и колонки подчинённых таблиц не меняются — сравнение с v5.4.0.2 в обсуждении
ТипыКолонок.Типы() и ТипыПодчиненныхТаблиц.Типы() отдавали общий изменяемый Массив Правка результата меняла перечень для всех ФиксированныйМассив

ОбъектМодели.Колонки() остаётся как в master: отдаёт саму таблицу модели, менять её контрактом запрещено.

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

  • Повторная регистрация типа возвращает существующий объект модели.
  • Типы() — ФиксированныйМассив: код, который менял результат, получит ошибку.
  • Служебный_ИмяТаблицы (помечен @internal) удалён. У конструктора ОбъектМодели новый необязательный параметр ПИмяТаблицы.
  • Зависимость collectionos поднята до 0.9.0.

Тесты

Новые:

  • МодельДанных — повторная регистрация не подменяет объект модели; задание обходит модель, пока тест регистрирует 50 сгенерированных классов. На master второй тест падал в задании три раза из трёх.
  • ОбъектМодели.ИмяТаблицыЗадаетсяПриСоздании.
  • ТипыКолонок — оба Типы() отдают ФиксированныйМассив.

Полный прогон после правок по ревью, без PostgreSQL: OneScript 2.2.0 — 304 из 304, 2.3.0-next+a02323a1 — 304 из 304. До ревью с PostgreSQL: 322 из 322 на обоих движках и 305 из 305 только PostgreSQL.

🤖 Generated with Claude Code

- МодельДанных хранит объекты модели в СинхронизированнаяКарта и обходит ее
  через ДляКаждого: регистрация класса параллельно операциям больше не роняет
  обход модели ("Collection was modified"). Повторная регистрация типа не
  подменяет объект модели, с которым уже работают хранилища.
- ОбъектМодели.Колонки() снова отдает копию: правка результата не меняет общую
  модель. Библиотека и встроенные коннекторы читают таблицу без копии через
  служебный Служебный_Колонки().
- Имя таблицы объекта модели подчиненной таблицы передается третьим параметром
  конструктора ОбъектМодели; мутатор Служебный_ИмяТаблицы удален.
- ТипыКолонок.Типы() и ТипыПодчиненныхТаблиц.Типы() отдают ФиксированныйМассив.

Изменения поведения: Колонки() - копия, как до nixel2007#151; Типы() нельзя менять.

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: 5532e5e9-5584-40c4-801a-8300a4663f73

📥 Commits

Reviewing files that changed from the base of the PR and between 58e761e and 7ecedd2.

📒 Files selected for processing (20)
  • docs/МенеджерСущностей.md
  • docs/МодельДанных.md
  • docs/ОбъектМодели.md
  • docs/ТипыКолонок.md
  • docs/ТипыПодчиненныхТаблиц.md
  • src/internal/Модули/ОбработкаКоллекций.os
  • src/internal/Модули/РаботаСКоннекторами.os
  • src/Классы/АбстрактныйКоннекторSQL.os
  • src/Классы/КоннекторInMemory.os
  • src/Классы/КоннекторJSON.os
  • src/Классы/КоннекторPostgreSQL.os
  • src/Классы/КоннекторSQLite.os
  • src/Классы/МенеджерСущностей.os
  • src/Классы/МодельДанных.os
  • src/Классы/ОбъектМодели.os
  • src/Модули/ТипыКолонок.os
  • src/Модули/ТипыПодчиненныхТаблиц.os
  • tests/МодельДанных.os
  • tests/ОбъектМодели.os
  • tests/ТипыКолонок.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/Классы/ОбъектМодели.os, docs/ОбъектМодели.md, tests/ОбъектМодели.os
Конструктор принимает необязательное имя таблицы. Колонки() возвращает копию, а Служебный_Колонки() — исходную таблицу. Тесты проверяют эти контракты.
Синхронизированная регистрация моделей
src/Классы/МодельДанных.os, src/Классы/МенеджерСущностей.os, docs/МодельДанных.md, docs/МенеджерСущностей.md, tests/МодельДанных.os
Хранилище использует синхронизированную карту. Повторная регистрация сохраняет существующий объект, а обход возвращает снимок. Тесты проверяют повторную и параллельную регистрацию.
Использование исходных колонок
src/internal/Модули/*, src/Классы/АбстрактныйКоннекторSQL.os, src/Классы/КоннекторInMemory.os, src/Классы/КоннекторJSON.os, src/Классы/КоннекторPostgreSQL.os, src/Классы/КоннекторSQLite.os
Внутренние модули и коннекторы получают колонки через Служебный_Колонки(). Имя таблицы для объекта модели передаётся конструктору.

Фиксированные массивы типов

Layer / File(s) Summary
Перечни типов и их контракты
src/Модули/ТипыКолонок.os, src/Модули/ТипыПодчиненныхТаблиц.os, docs/ТипыКолонок.md, docs/ТипыПодчиненныхТаблиц.md, tests/ТипыКолонок.os
Перечни типов представлены фиксированными массивами. Их состав и порядок сохранены. Тесты проверяют тип и размер массивов.

Priority: ➖ Normal

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

Change: Bug fix

Suggested reviewers: nixel2007

Merge Risk: ⚪ Minimal · up to 7eced

The changes are mergeable after normal checks. No actionable failure was identified; the concurrency tests exercise registration alongside model enumeration.

Architecture Summary

Architecture risk: 🔵 Low · up to 7eced

The change affects 3 systems.

Changed systems: src, docs, tests

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 12 changed files map to changed impact.
  • observed — docs (service) was modified; 5 changed files map to changed impact.
  • observed — tests (service) was modified; 3 changed files map 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
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

Autopilot is currently an internal CodeRabbit preview.


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.

ОбъектМоделиЭлементКоллекции.Служебный_ИмяТаблицы(ПодчиненнаяТаблица.ИмяТаблицы);
// Колонки модели только для чтения, но этот объект модели только что создан и еще никому не отдан.
КолонкиОбъектаМодели = ОбъектМоделиЭлементКоллекции.Колонки();
КолонкиОбъектаМодели = ОбъектМоделиЭлементКоллекции.Служебный_Колонки();

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.

у тебя протух апстрим. Служебный_Колонки() больше не нужен. Колонки() теперь возвращает оригинальную таблицу, но контрактом ее запрещено менять, чтобы не создавать гонку.

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.

Убрал в a682855. Колонки() снова отдаёт саму таблицу с контрактом «только для чтения», как после #151. Служебный_Колонки() и копии больше нет: модули и коннекторы такие же, как в master, тесты #151 (КолонкиОтдаютТаблицуМодели, индексы, донастройка через Колонки()) вернул. ОбработкаКоллекций правит Колонки() только у свежего, ещё никому не отданного объекта модели элемента.

Свежий master тоже влил, но с тех пор там пришли только обновления зависимостей.

@@ -1,25 +1,60 @@
#Использовать collectionos

// СинхронизированнаяКарта - Тип сущности: ее объект модели. Модель общая у всех потоков, работающих

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, раз она не меняется после инициализации.

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.

Сделал в a682855. Модель хранится в КартаКопированиеПриЗаписи из collectionos 0.9.0 (поднял в packagedef):

  • Получить и обход идут по неизменяемому снимку без блокировки, ПолучитьОбъектыМодели — Значения().ВМассив();
  • регистрация — ВставитьЕслиОтсутствует: повторная регистрация возвращает уже существующий объект модели и не подменяет его.

Тесты МодельДанных (регистрация 50 классов параллельно обходу модели, повторная регистрация) — по три прогона на 2.2.0 и 2.3.0-next, все зелёные. Полный прогон без PostgreSQL — 304 из 304 на обоих движках.

Перем Ссылка Экспорт;
Перем ДвоичныеДанные Экспорт;

// ФиксированныйМассив - Все типы колонок.

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.

ФиксированныйМассив из ...

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.

Поправил в a682855: ФиксированныйМассив из Строка у Типы и ПримитивныеТипы в ТипыКолонок и у Типы в ТипыПодчиненныхТаблиц.

// Параметры:
// ПТипСущности - Тип - Тип класса сущности.
// ПМодельДанных - МодельДанных - Модель, к которой относится объект.
// ПИмяТаблицы - Строка - Имя таблицы вместо имени из аннотации класса. Так создаются объекты

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.

ну-ка подробнее, как оно работает на релизной версии с примерами разных типов подчиненных таблиц, и как оно работает после этой правки.

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.

Поведение не меняется. Ниже — как это работает на релизе v5.4.0.2 и что поменяла правка; проверено одним сценарием на релизе и на ветке.

Как сейчас (v5.4.0.2)

  1. Родительский ОбъектМодели в ЗаполнитьПодчиненныеТаблицы описывает каждое поле с &ПодчиненнаяТаблица:

    • имя таблицы — ИмяТаблицы из аннотации, а без него Строка(ТипСущности) + "_" + ИмяПоля;
    • Тип — Массив или Структура, плюс ТипЭлемента и КаскадноеЧтение.
  2. Для записи, чтения и Инициализировать ОбработкаКоллекций.ПолучитьОбъектМоделиДляПодчиненнойТаблицы каждый раз строит новый ОбъектМодели служебного класса СлужебнаяСущность_ЭлементКоллекцииКлючЗначение (колонки id, ref, key, value). Конструктор берёт имя таблицы из &Сущность служебного класса — СлужебнаяСущность_ЭлементКоллекцииКлючЗначение.

  3. Затем Служебный_ИмяТаблицы(ПодчиненнаяТаблица.ИмяТаблицы) подменяет имя таблицы, и Колонки() этого свежего объекта донастраиваются:

    • key — Целое для массива и Строка для структуры;
    • у ref ТипСсылки — тип родителя;
    • value — примитивный тип элемента либо Ссылка на класс элемента.

    Объект никому не передан, поэтому эти правки контракт не нарушают.

Что изменилось. Имя таблицы передаётся третьим параметром конструктора и присваивается в его конце, то есть там же, где раньше его ставил Служебный_ИмяТаблицы сразу после конструктора. После ЗаполнитьКолонки конструктор ИмяТаблицы не читает: имя подчинённых таблиц по умолчанию собирается из ТипСущности. Поэтому результат тот же. Ушёл только экспортный мутатор, через который имя таблицы можно было поменять у уже опубликованного объекта модели.

Примеры. Это СущностьСоВсемиТипамиКолонок из фикстур и класс Заказ с таблицей Заказы и тремя подчинёнными таблицами без ИмяТаблицы (массив Целое, структура Дата, массив ссылок). SQLite, сохранение и чтение. Вывод на v5.4.0.2 и на ветке совпадает строка в строку, на 2.2.0 и на 2.3.0-next:

СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок)
  Массив: Массив из Строка -> таблица ВсеТипыКолонок_Массив; key Целое; ref Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок); value Строка
  Структура: Структура из Строка -> таблица ВсеТипыКолонок_Структура; key Строка; ref Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок); value Строка
  МассивСсылок: Массив из СущностьСоВсемиТипамиКолонок -> таблица ВсеТипыКолонок_МассивСсылок; key Целое; ref Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок); value Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок)
  МассивСсылокКаскад: Массив из СущностьСоВсемиТипамиКолонок, каскадно -> таблица ВсеТипыКолонок_МассивСсылокКаскад; key Целое; ref Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок); value Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок)
Заказ (таблица Заказы)
  Числа: Массив из Целое -> таблица Заказ_Числа; key Целое; ref Ссылка -> Заказ (таблица Заказы); value Целое
  Сроки: Структура из Дата -> таблица Заказ_Сроки; key Строка; ref Ссылка -> Заказ (таблица Заказы); value Дата
  Позиции: Массив из СущностьСоВсемиТипамиКолонок -> таблица Заказ_Позиции; key Целое; ref Ссылка -> Заказ (таблица Заказы); value Ссылка -> СущностьСоВсемиТипамиКолонок (таблица СущностьСоВсемиТипамиКолонок)
Таблицы в БД: sqlite_sequence, ВсеТипыКолонок_Массив, ВсеТипыКолонок_МассивСсылок, ВсеТипыКолонок_МассивСсылокКаскад, ВсеТипыКолонок_Структура, Заказ_Позиции, Заказ_Сроки, Заказ_Числа, Заказы, СущностьСоВсемиТипамиКолонок
Заказ_Числа: (1, 0, 30) (1, 1, 10) (1, 2, 20)
Заказ_Сроки: (1, Начало, 01.10.2026 0:00:00) (1, Конец, 07.10.2026 0:00:00)
Заказ_Позиции: (1, 0, 7)
Прочитано: Числа = 30, 10, 20; Сроки = 01.10.2026 .. 07.10.2026; Позиции[0] = 7 (Число)

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

sfaqer and others added 2 commits October 7, 2026 08:44
…ированиеПриЗаписи

По ревью:
- Колонки() по-прежнему отдает саму таблицу модели, менять ее контрактом
  запрещено; Служебный_Колонки() и копия убраны, тесты nixel2007#151 возвращены.
- Модель данных меняется только при регистрации класса, поэтому хранится в
  КартаКопированиеПриЗаписи (collectionos 0.9.0): чтение и обход идут по
  неизменяемому снимку, ПолучитьОбъектыМодели - Значения().ВМассив().
- В комментариях перечней указан тип элементов ФиксированныйМассив.

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