# C++ RAM and mmap replay callback lifetime

The C++ memory, async-file and mmap stores now reacquire a pending replay view
after an accepted gap callback. Callback-time close previously exposed freed
or unmapped memory to OnMessage; reset, eviction and replacement could deliver
stale payloads or metadata. The fix adds no lock or allocation and leaves
contiguous replay unchanged. This extends the separately published QuickFIX
correction, 2f7cdd9ab, without modifying the journal-lock owner's files.

## Reproduced failures

Baseline source is 54b7ff14e with the new tests and their registration only.
Store sequence 2, replay 1 through 2, and act on the store in OnGap(1, 2).
Each action runs on the store's owner thread; these are not concurrent-reader
tests.

Closing the async-file store frees its RAM window. The subsequent OnMessage
read produced an AddressSanitizer heap-use-after-free, including the allocation,
release and callback stacks. Closing the mmap store unmaps the pending bytes;
the regular test process received SIGSEGV when OnMessage copied them.

Six further baseline commands failed assertions: clear on each of the three
stores, one-slot eviction on memory and mmap, and replacement on mmap. The
first five still delivered a message that was no longer indexed. Replacement
delivered the old mapped payload and metadata rather than the new record.
Three controls passed: reading another record in each store leaves these
payloads stable, unlike QuickFIX's shared file-read buffer.

## Correction and persistent tests

After OnGap returns true, each ReplayOutbound calls FindOutbound again. If
the callback closes, clears or evicts the pending frame, replay returns false
without calling OnMessage. Replacement delivers the current payload, length
and timestamp. Rejecting OnGap still stops immediately; no reacquisition is
performed. No view or iterator is retained across that callback for delivery.
The extra work is a RAM/index lookup at a gap boundary, not a change to fresh
sends. No measured latency improvement is claimed.

The new open test covers memory and async-file stores; its pro wrapper adds
mmap without making the open tree include a private header. Both are registered
in their correctness lists. Fourteen memory scenarios and 16 each for file
and mmap make 46 distinct cases: shorter/longer other payloads, benign read,
nested replay, replacement, eviction, clear, close where supported, gap
cancellation and message cancellation. Replacement also checks the timestamp.
The pro executable includes all 46; the open executable includes 30.

These tests do not allow retaining an OnMessage view across later store changes,
or using an owner-thread store from competing threads. They ensure replay itself
does not hand a view invalidated by an earlier callback to the next callback.

## Verification and evidence

GCC 11.5 and Clang 21.1 pass the new tests, existing file-store and async-runner
tests, architecture suite and development mmap suite. GCC also passes both
QuickFIX physical-store, protocol-session and real-socket suites in each
edition. Together these are 28 direct suite executions. The open and pro
callback executables pass AddressSanitizer, UndefinedBehaviorSanitizer and leak
detection, including the two close cases that failed on baseline.

The normal GCC 14 Makefile builds and runs the open target and both development
targets, adding three successful executions. Its first check caught a missing
explicit link rule for the new open test; that rule was added before these
successful builds. The original failed Makefile log is retained. Export scrub
reports seven changed engine files and zero findings; the edition list includes
the new test and its private companion separately.

Evidence is under `~/archive/libhft/store-replay-callback-review-20261003/`:
baseline pin, test patch and new-source copies, all eight failing commands and
three controls, sanitizer stacks, direct and final verification patches,
compiler/build/run logs and seven final source hashes. Direct verification
preceded only the Makefile link-rule addition; final checks compiled the same
runtime/test sources through that corrected rule. The CPU-slot helper wrapped
both verification stages. Some functional checks used the isolated pool while
shared work was permitted; none supplies a quiet-host timing result.

## Other flavours and remaining limits

Read-only inspection found a different lifetime arrangement in Rust: QuickFIX
messages reside in owned per-record allocations, and replay borrows the store
immutably while mutation requires a mutable borrow. Java checks presence before
emitting a gap and reads the stored bytes afterward. .NET's managed replay does
not hand a future borrowed view across a gap callback; its next replay step
reads again. These source observations are not new Java/Rust/.NET test results
or proof that their recovery code has no other defects.

This is affected-path verification, not a full correctness sweep. No Windows
or NIC run is claimed and no installed binary was replaced. The separate
journal lock work, 64-bit sequence migration and .NET fixture false-pass remain
with their active owners.
