Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions pam_pmm.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,37 +158,61 @@ static_assert( std::is_trivially_copyable<pam_pmm_registry>::value,
"pam_pmm_registry должен быть тривиально копируемым" );

// ═══════════════════════════════════════════════════════════════════════════
// ГЛОБАЛЬНОЕ СОСТОЯНИЕ PAM_PMM
// ГЛОБАЛЬНОЕ СОСТОЯНИЕ PAM_PMM (Этап 10.1: инкапсуляция в структуру)
// ═══════════════════════════════════════════════════════════════════════════

namespace detail
/**
* @brief Инкапсулированное состояние PMM-фасада.
*
* Три ранее разрозненные статические переменные (filename, root_offset,
* initialized) объединены в одну структуру. Это первый шаг к поддержке
* нескольких экземпляров БД в одном процессе (Проблема 3, plan.md).
*
* Текущая реализация использует глобальный синглтон (pam_pmm_global_state()),
* а функции detail:: делегируют ему. В дальнейшем (Этап 10.1b) состояние
* будет передаваться как явный параметр.
*/
struct pam_pmm_state
{
char filename[256] = {}; ///< Имя файла хранилища
uintptr_t root_offset = 0; ///< Смещение корневой структуры в ПАП
bool initialized = false; ///< Флаг инициализации

/// Сбросить все поля к начальным значениям.
void reset()
{
filename[0] = '\0';
root_offset = 0;
initialized = false;
}
};

/// Имя файла хранилища (глобальное состояние).
inline char& pam_pmm_filename_char( std::size_t idx )
/// Глобальный синглтон состояния PMM.
inline pam_pmm_state& pam_pmm_global_state()
{
static char filename[256] = {};
return filename[idx];
static pam_pmm_state state;
return state;
}

namespace detail
{

/// Имя файла хранилища (делегирует глобальному состоянию).
inline char* pam_pmm_filename()
{
return &pam_pmm_filename_char( 0 );
return pam_pmm_global_state().filename;
}

/// Смещение корневой структуры в ПАП (глобальное состояние).
/// Корневая структура аллоцируется первой и хранится по известному смещению.
/// Смещение корневой структуры в ПАП (делегирует глобальному состоянию).
inline uintptr_t& pam_pmm_root_offset()
{
static uintptr_t offset = 0;
return offset;
return pam_pmm_global_state().root_offset;
}

/// Флаг инициализации.
/// Флаг инициализации (делегирует глобальному состоянию).
inline bool& pam_pmm_initialized()
{
static bool initialized = false;
return initialized;
return pam_pmm_global_state().initialized;
}

} // namespace detail
Expand Down
28 changes: 8 additions & 20 deletions plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
| 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено | ✅ |

**Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования.
**Тесты:** 655 тестов, ~360 000 assertion.
**Тесты:** 659 тестов, ~360 000 assertion.

---

Expand All @@ -39,25 +39,12 @@

---

### Проблема 3: Глобальное состояние в pam_pmm.h
### ~~Проблема 3: Глобальное состояние в pam_pmm.h~~ (Этап A ✅)

**Файл:** `pam_pmm.h`, строки 164–194 (namespace detail)
**Решено (Этап A) в Этапе 10.1:** Три разрозненные статические переменные (`filename`, `root_offset`, `initialized`) инкапсулированы в структуру `pam_pmm_state` с методом `reset()`. Глобальный синглтон `pam_pmm_global_state()` заменяет прямой доступ к переменным. Функции `detail::` делегируют синглтону для обратной совместимости. Добавлены 4 теста.

Три статические переменные хранят глобальное состояние PMM:

```cpp
static char filename[256] = {};
static uintptr_t offset = 0;
static bool initialized = false;
```

Это делает невозможным:
- Одновременную работу с несколькими БД в одном процессе
- Потокобезопасную инициализацию (data races при конкурентном вызове `pam_pmm_init`)
- Юнит-тестирование с изоляцией (тесты зависят от глобального состояния)

**Решение (поэтапно):**
1. **Этап A:** Инкапсулировать три переменные в структуру `pam_pmm_state`
**Остающиеся этапы (будущие задачи):**
1. ~~**Этап A:** Инкапсулировать три переменные в структуру `pam_pmm_state`~~ ✅
2. **Этап B:** Передавать `pam_pmm_state&` как явный параметр вместо обращения к глобальным переменным
3. **Этап C:** Опционально — защита `std::mutex` для потокобезопасной инициализации

Expand Down Expand Up @@ -202,7 +189,7 @@ pvector был бы предпочтительнее **только** при ч

| # | Проблема | Файл | Сложность | Влияние |
|---|----------|------|-----------|---------|
| 3 | Глобальное состояние PMM | pam_pmm.h | Высокая | Архитектура |
| ~~3~~ | ~~Глобальное состояние PMM (Этап A)~~ | ~~pam_pmm.h~~ | ~~Высокая~~ | |
| 7 | Нет escaping '/' в путях | pjson_db_pmm.h | Средняя | Совместимость |
| 10 | Многократный resolve в is_*() | pjson_node.h | Средняя | Производительность |
| 11 | const-корректность _walk_path | pjson_db_pmm.h | Средняя | Корректность |
Expand All @@ -227,7 +214,7 @@ pvector был бы предпочтительнее **только** при ч
→ тесты: все 655 тестов проходят

Этап 10: Приоритет 3 — архитектурные улучшения
10.1 Инкапсуляция глобального состояния pam_pmm
10.1 Инкапсуляция глобального состояния pam_pmm (Этап A: структура pam_pmm_state + синглтон)
10.2 Поддержка RFC 6901 (JSON Pointer) для путей
10.3 Оптимизация tag-проверок на горячих путях
10.4 const-корректность с явной передачей состояния
Expand All @@ -239,6 +226,7 @@ pvector был бы предпочтительнее **только** при ч

| Дата | Изменение |
|------|-----------|
| 2026-03-22 | Этап 10.1: инкапсуляция глобального состояния pam_pmm в структуру pam_pmm_state (Issue #205) |
| 2026-03-22 | Этап 9.4: parse_object() в один проход без двойного парсинга (Issue #192) |
| 2026-03-22 | Этап 9.3: _free_node_tree и _resolve_refs_in_subtree через pjson_traverse_subtree с visitor-функторами (Issue #191) |
| 2026-03-22 | Этап 9.2: прямая вставка node_id в массив/объект без временных слотов (Issue #190) |
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ int main() {
| `pjson_db_pmm.h` | D | Менеджер персистной JSON-БД: path-адресация, `put`/`get`/`erase`, `$ref`, метрики, поиск, клонирование |
| `deps/pmm/pmm.h` | A | [PersistMemoryManager](https://github.com/netkeep80/PersistMemoryManager) — бэкенд ПАП |
| `main.cpp` | — | Демонстрационная программа |
| `tests/` | — | Тесты на Catch2 (655 тестов, ~360 000 assertion) |
| `tests/` | — | Тесты на Catch2 (659 тестов, ~360 000 assertion) |
| `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) |

---
Expand Down Expand Up @@ -438,7 +438,7 @@ db.put("/copy/name", "Bob");

## Известные ограничения

- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3)
- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях
- **Нет escaping `/` в путях** — ключи объектов, содержащие `/`, недоступны через path-адресацию (см. [plan.md](plan.md), Проблема 7)
- ~~**Утечка временных узлов метрик**~~ — **Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик
- **Не потокобезопасно** — CacheManagerConfig (по умолчанию) использует NoLock; для многопоточности нужен PersistentDataConfig
Expand Down
51 changes: 51 additions & 0 deletions tests/test_pam_pmm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,54 @@ TEST_CASE( "pam_pmm: root object survives reset", "[pam_pmm]" )

pam_pmm_destroy();
}

// ═══════════════════════════════════════════════════════════════════════════
// ТЕСТЫ pam_pmm_state (Этап 10.1: инкапсуляция глобального состояния)
// ═══════════════════════════════════════════════════════════════════════════

TEST_CASE( "pam_pmm_state: default construction zeroed", "[pam_pmm][pam_pmm_state]" )
{
pam_pmm_state s{};
REQUIRE( s.filename[0] == '\0' );
REQUIRE( s.root_offset == 0 );
REQUIRE( s.initialized == false );
}

TEST_CASE( "pam_pmm_state: reset clears all fields", "[pam_pmm][pam_pmm_state]" )
{
pam_pmm_state s{};
std::strncpy( s.filename, "test.pam", sizeof( s.filename ) - 1 );
s.root_offset = 42;
s.initialized = true;

s.reset();

REQUIRE( s.filename[0] == '\0' );
REQUIRE( s.root_offset == 0 );
REQUIRE( s.initialized == false );
}

TEST_CASE( "pam_pmm_state: global_state singleton is consistent with detail:: accessors", "[pam_pmm][pam_pmm_state]" )
{
pam_pmm_state& gs = pam_pmm_global_state();

// detail:: functions should return pointers/references into the same state.
REQUIRE( detail::pam_pmm_filename() == gs.filename );
REQUIRE( &detail::pam_pmm_root_offset() == &gs.root_offset );
REQUIRE( &detail::pam_pmm_initialized() == &gs.initialized );
}

TEST_CASE( "pam_pmm_state: init/destroy cycle reflected in global state", "[pam_pmm][pam_pmm_state]" )
{
pam_pmm_init( nullptr );

pam_pmm_state& gs = pam_pmm_global_state();
REQUIRE( gs.initialized == true );
REQUIRE( gs.root_offset != 0 );

pam_pmm_destroy();

REQUIRE( gs.initialized == false );
REQUIRE( gs.root_offset == 0 );
REQUIRE( gs.filename[0] == '\0' );
}
Loading