Skip to content

fix(instrumented-pool): correctness fixes (data race, live underflow, move-assign event)#99

Merged
danielPoloWork merged 1 commit into
masterfrom
fix/instrumented-pool-correctness
Jun 15, 2026
Merged

fix(instrumented-pool): correctness fixes (data race, live underflow, move-assign event)#99
danielPoloWork merged 1 commit into
masterfrom
fix/instrumented-pool-correctness

Conversation

@danielPoloWork

Copy link
Copy Markdown
Owner

Summary

Fixes three verified, third-party-reported defects in the InstrumentedPool Decorator (ADR-0025/0026), recorded in the bug ledger (ADR-0039) and targeted at the v1.1.2 PATCH. No public API change — header-only + a C-header doc correction.

Motivation

Reported externally; I reproduced/root-caused each against the code and the documented contracts before accepting (the §7.7 triage flow). All three confirmed real and not excused by documented behaviour. Records: BUG-0001, BUG-0002, BUG-0003.

Changes

  • BUG-0001 (high) — data race. notify_if_grew() read+wrote the non-atomic last_growths_ on the allocation hot path, racing under the documented-safe MUTEX + dynamic-growth + concurrent config (UB). last_growths_ is now std::atomic, advanced with a compare_exchange so grew fires once per growth without a race. Added a concurrent InstrumentedPool case to the ThreadSanitizer stress suite.
  • BUG-0002 (medium) — counter underflow. deallocate() decremented the unsigned live_ for any non-null pointer, so a foreign/double-freed pointer (a core no-op, ADR-0012) wrapped it to SIZE_MAX. The decrement now clamps at zero. The memory_pool.h double-free note was corrected — the Decorator counts but does not detect a double-free, so it no longer claims to address it.
  • BUG-0003 (low) — missing lifecycle event. Move-assignment released the destination pool + observers without emitting PoolEvent::destroyed, asymmetric with the destructor. It now notifies before reassignment.
  • Bug ledger records (status fixed, fixed-in: v1.1.2) + index; CHANGELOG Fixed entries; two unit tests (clamp, move-assign event) + one concurrency test (BUG-0001).

Design Patterns

  • None — correctness fixes within the existing Decorator/Observer (ADR-0025/0026).

Verification

  • clang-format clean; clang-tidy clean — headers + both test TUs compile (incl. the MUTEX-gated concurrency case via -DPBR_MEMORY_POOL_THREAD_SAFETY=1)
  • python tools/consistency_lint.py — OK (the bugs check validates the 3 records)
  • markdownlint-cli2 clean
  • Full build matrix + ASan/UBSan/TSan (BUG-0001) — validated by CI (local box is MSVC-only, no TSan)

Documentation Impact

  • Bug ledger records + index; CHANGELOG Fixed; C-header doc corrected
  • No README / ROADMAP / ADR change (maintenance fix, not a feature; PATCH per maintenance.md)
  • PR metadata set — assignee, fix label, v1.1.2 milestone (§6.4 / ADR-0040)

Follow-up

A second PR (after this merges) fixes the latent core overflow in grow_pool (the LOW-severity BUG-0004), keeping one component per PR.

🤖 Generated with Claude Code

… move-assign event)

Three verified third-party-reported defects in the InstrumentedPool
Decorator (ADR-0025/0026), recorded in the bug ledger (ADR-0039) and
fixed for v1.1.2. No public API change; header-only.

- BUG-0001 (high): notify_if_grew() read+wrote the non-atomic
  last_growths_ on the allocation hot path, racing under the
  documented-safe MUTEX + dynamic-growth + concurrent configuration
  (UB). Make last_growths_ std::atomic and advance it with a
  compare_exchange so `grew` fires once per growth without a race. Add
  a concurrent InstrumentedPool case to the TSan stress suite.
- BUG-0002 (medium): deallocate() decremented the unsigned live_ for any
  non-null pointer, so a foreign/double-freed pointer (a core no-op,
  ADR-0012) underflowed it to SIZE_MAX. Clamp the decrement at zero.
  Correct the memory_pool.h double-free note: the Decorator counts but
  does not detect a double-free (it cannot distinguish one) — it no
  longer claims to address it.
- BUG-0003 (low): move-assignment released the destination pool and
  observers without emitting PoolEvent::destroyed, asymmetric with the
  destructor. Notify before reassignment.

Verified locally with clang-format / clang-tidy (headers + both test
TUs compile clean, incl. the MUTEX-gated concurrency case) and the
consistency lint; CI runs the full matrix + ASan/UBSan/TSan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danielPoloWork danielPoloWork added this to the v1.1.2 milestone Jun 15, 2026
@danielPoloWork danielPoloWork added the fix Bug fix (Conventional Commit: fix) label Jun 15, 2026
@danielPoloWork danielPoloWork self-assigned this Jun 15, 2026
@danielPoloWork danielPoloWork merged commit 424cac2 into master Jun 15, 2026
34 checks passed
danielPoloWork added a commit that referenced this pull request Jun 15, 2026
…rflow (#100)

## Summary
Closes the latent `size_t` overflow in `grow_pool`'s growth-size
computation (BUG-0004) — the last of the four verified
third-party-reported defects. Core-only, no API change.

## Motivation
`grow_pool` computed `total * (grow_factor_ - 1)` **before** any
overflow check, then validated `add * block_size_` — so the first
multiplication could wrap `size_t` and feed the downstream guard an
already-wrapped value. Inconsistent with the meticulous overflow
handling on the create path (ADR-0009 §3). Record:
[BUG-0004](docs/bugs/2026/06/BUG-0004-grow-pool-growth-size-overflow.md).

## Changes
- `memory_pool.cpp` (`grow_pool`) — add `would_overflow_product(total,
grow_factor_ - 1)` guard before computing `add`; on overflow the pool
falls back to fixed-mode exhaustion, exactly as the existing guards do.
- Bug ledger record BUG-0004 (`fixed`, `fixed-in: v1.1.2`) + index;
`CHANGELOG` `Fixed` entry.

## Design Patterns
- None — a defensive guard within the existing dynamic-growth path
(ADR-0022/0024).

## Verification
- [x] `clang-format` + `clang-tidy` clean on `memory_pool.cpp`
(`grow_pool` compiles under the default build)
- [x] `python tools/consistency_lint.py` — OK; `markdownlint-cli2` clean
- [x] No test added — the overflow is **not runtime-reachable** through
the public API (RAM exhausts long before `total` nears the boundary);
the record documents why
- [ ] Full build matrix — CI

## Documentation Impact
- [x] Bug ledger record + index; `CHANGELOG` `Fixed`
- [x] No README / ROADMAP / ADR change (maintenance fix; PATCH per
maintenance.md)
- [x] PR metadata set — assignee, `fix` label, `v1.1.2` milestone (§6.4
/ ADR-0040)

## Context
Second and final fix PR for the four reported defects (PR #99 covered
the three `InstrumentedPool` issues). After this merges, `v1.1.2` is
ready to cut whenever you choose.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@danielPoloWork danielPoloWork mentioned this pull request Jun 15, 2026
6 tasks
danielPoloWork added a commit that referenced this pull request Jun 15, 2026
## Summary
Cuts the **v1.1.2** maintenance release — a **PATCH** bundling the four
verified bug fixes (BUG-0001…0004, the first real use of the in-repo bug
ledger) and the documentation work accumulated since `v1.1.1`. The
library's public surface is unchanged (no API/ABI change). Full notes:
[`docs/releases/v1.1.2.md`](docs/releases/v1.1.2.md).

## Motivation
SemVer PATCH per the [maintenance decision
tree](docs/workflow/maintenance.md) (no public-surface change).
Mechanics per [`docs/workflow/release.md`](docs/workflow/release.md).

## Changes
- **`version.hpp`** — PATCH `1 → 2`, STRING `"1.1.1" → "1.1.2"`;
`pool_smoke` version `TEST_CASE` updated.
- **`CHANGELOG.md`** — `[Unreleased]` rolled into the immutable
[`docs/changelog/v1/v1.1.2.md`](docs/changelog/v1/v1.1.2.md) (links
re-based), `Unreleased` reset, index row added, compare link →
`v1.1.2...HEAD`.
- **`docs/releases/v1.1.2.md`** — draft GitHub Release notes.
- **`README.md`** — status badge → `v1.1.2` + a new `v1.1.2` status
paragraph.
- **i18n** — the two README translation rows marked `stale` (English
README changed); the `zh-Hans`/`ja` re-sync is a follow-up. The
`i18n-freshness` lint skips `stale` rows, so the gate stays green.

## Release contents
**Fixed:** BUG-0001 (InstrumentedPool growth-counter data race),
BUG-0002 (`live_` underflow + header doc), BUG-0003 (move-assign
`destroyed` event), BUG-0004 (`grow_pool` overflow guard). **Removed:**
the `docs-site` README badge. **Changed:** `zh-Hans`/`ja` README
re-synced to v1.1.1. ADR total stays 40.

## Design Patterns
- None — release bookkeeping.

## Verification
- [x] `python tools/consistency_lint.py` — OK (version-lockstep sees
`version.hpp`/CHANGELOG/README badge/release notes all at `1.1.2`;
`i18n-freshness` green)
- [x] `markdownlint-cli2` + `clang-format` clean
- [ ] CI build matrix + ASan/UBSan/TSan — validated on this PR (the bug
fixes themselves landed and passed CI in #99/#100)

## Documentation Impact
- [x] README badge + paragraph; CHANGELOG rolled; release notes added
- [x] ROADMAP — N/A (maintenance PATCH, no milestone change)
- [x] PR metadata set — assignee, `chore` label, `v1.1.2` milestone
(§6.4 / ADR-0040)

## After merge
The agent tags `v1.1.2` from `master`
([ADR-0008](docs/adr/0008-delegate-tag-creation-and-push-to-the-agent.md));
`release.yml` produces a **draft** GitHub Release; the maintainer clicks
*Publish*. Then a follow-up PR re-syncs the `zh-Hans`/`ja` README
translations to `v1.1.2` and closes the `v1.1.2` milestone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix (Conventional Commit: fix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant