# QuickFIX I/O retirement and FIXP mmap handoff — 2026-10-09

Session `codex-review-recovery-1009` in private `atrekes/libhft-devel`, starting at
`d09475507`. C++, Java and Rust agents reviewed disjoint paths in this session's
worktree; root ran all builds and tests under the shared host CPU lease, checking
processes and memory and capping parallelism at 16. Outputs used an absolute
`LIBHFT_BUILD_ROOT`; main was read/pulled only until publication.

## QuickFIX-compatible stores after an I/O failure

C++ and Java compatibility stores returned failure after append/checkpoint I/O but
accepted later writes. A partial header could cease to be a recoverable tail when
another append followed it. Checkpoint retries and automatic reset could also hide
or erase the failed epoch. This is the analogous contract to the Rust repair in the
preceding replay-durability review.

Both layouts now latch ordinary write failures. Later append, sequence changes and
normal resets refuse without mutating the failed files; Java sync refuses too.
Invalid arguments do not poison healthy stores. A complete body/index publication
still consumes its outbound sequence when the later checkpoint fails, and remains
readable. Explicit sequence setters retain their previous cached values on failure.
Writer ownership stays held until close.

C++ reset previously cleared its index/counters before fallible multi-file I/O and
closed on error, releasing both writer fences. It now publishes the new epoch only
after all reset operations succeed. Failure retains old counters and ownership,
and suppresses replay through offsets that may address truncated data. An explicit
`TryResetOffline` may recover the retained object; peer-logon and teardown resets
cannot clear the fault. The configured-layout facade and runner helpers forward
that distinction. Java normal reset refuses an earlier write failure and requires
close/reopen for recovery; existing failed-reset retirement remains intact.

The C++ regression has 136 cases across direct/configured C++ and QuickFIX/J layouts.
It covers seek/write/flush errors, positive partial writes, each destructive reset
stage, retrying explicit repair, payload/sequence visibility, ownership refusal and
healthy invalid-argument controls. Eight cases use real Linux `RLIMIT_FSIZE` faults
for positive partial body/index writes and verify bytes remain unchanged through
close and are recoverable as a final tail. Synthetic flush cases flush actual bytes
then report error, modelling uncertain progress. The original passes four controls
and fails 132 cases; corrected optimized and ASan/UBSan runs pass all 136.

The Java regression has 41 cases: 30 seek/zero/positive-partial write cases, nine
closed-channel force failures, and two healthy controls. Partial writes reach the
live data descriptors; only the injected delegate is restored, so continued refusal
proves sticky failure rather than a still-broken test descriptor. Two force cases
also use an external JVM to check both writer fences before/after close. Original
code fails 39 cases. Both reset refusal and teardown preserve captured file bytes.
Cold reopen is checked where checkpoint files remain valid; a torn sequence rewrite
may still need explicit file repair.

C++ compatibility profiles remain buffered checkpoint backends; this pass adds no
fsync durability claim or new sync-before-send policy. Java tests exercise force
failure at each file, rather than representing a closed descriptor as a real disk
sync operation.

## FIXP mmap primary handoff in C++, Java and Rust

Releasing the primary lock left a writable mapping and stale append cursor in all
three implementations. After a successor wrote another record, the old owner could
overwrite it or reset its file. Reacquiring the lock alone did not recover changes
made by the successor. Repeated mapping also replaced live mapped state.

Release now retires the mapping before handing off ownership. Reacquisition keeps
writes unavailable until explicit mapping/recovery rebuilds the cursor and index.
A split open/lock/map caller cannot map after losing arbitration; requesting a lock
records the fenced lifecycle. Already mapped non-HA handles refuse an in-place HA
upgrade without changing their existing non-HA use. Repeated mapping refuses before
resize or recovery. Payload and finalized-session history are both recovered under
the primary lock. Java moved the history refresh into that common recovery boundary.

Independent cross-review found a further empty-file boundary: a successor can
truncate successfully and fail before restoring file size. Remapping that empty file
on the retained old object wrote a new header but kept its stale replay index and
sequence/watermark state. The fresh-file branch now clears that state after a
successful mapping, while retaining the separate finalized-session history. C++
reproduces the actual truncate-success/resize-failure with `RLIMIT_FSIZE=0`; Java and
Rust model the boundary by truncating through the successor's owned descriptor.

New suites contain 16 C++ cases, ten Java cases and ten Rust methods. They cover old
owner writes/reset/replay, successor records and reset epochs, explicit recovery,
finalization history, duplicate mapping, failed arbitration and healthy non-HA use.
Java also checks failed lock-release retry and newly corrupt finalization history
refusal before recovery changes the replay file. Original code fails 15 C++, nine
Java and nine Rust cases, with one healthy control passing in each suite. Corrected
runs pass; the 16 C++ cases also pass ASan/UBSan.

Java's existing release/reacquire ownership fixture now uses the split lifecycle,
remaps before resumed writes and checks actual successor payload recovery. Its
external-process lock checks remain. Java explicit release retains ownership if
force fails and retains the lock handle if unlock fails; close remains best effort.
C++/Rust retain their best-effort void flush/unlock error policy. Their handoff tests
do not claim fault-injected flush/unlock durability.

## Verification

| Gate | Result |
|---|---|
| C++11 open edition, layering, simulator | 151/151 binaries pass, no reported skips; 84 private binaries excluded |
| Affected private C++ mmap/runner/selection binaries | 9/9 pass, no reported skips |
| New C++ optimized + ASan/UBSan suites | 136 QuickFIX and 16 handoff cases pass in each build |
| Java development with mandatory native library | 6,040 unique cases pass |
| Java open with separately built native library | 5,587 unique cases pass |
| Rust default + no-default-features | 1,472 pass in each, one existing ignored documentation example |
| Rust all-target default Clippy | Pass with warnings denied |
| Export-name scan | 16 engine files, zero findings |

Java totals deduplicate class/case names and have zero final failures/errors/skips.
The three counters-disabled conditional skips in the ordinary fork pass in their
configured fork. Native-loading guards pass; neither edition relies on missing
native code being skipped. Original-code counterfactual runs and all final reports
are retained, including the first Rust focused run before the empty-epoch follow-up.

Independent reviewers checked the other languages' changes. The two cross-review
corrections (fresh empty epochs and preserving healthy non-HA admission) were made
before broad final verification. No per-message lock, allocation or ownership check
was added to mmap save/read paths. The former mappings are retired at cold lifecycle
boundaries. No throughput or latency percentile was measured.

Execution was on Linux. Windows branches were reviewed but not run; Linux's actual
file-size-limit faults are not proof of Windows I/O behavior. This was an affected
engine review and the stated suites, not an all-language conformance run. The earlier
.NET aliasing proposal remains separate while its paths are reserved; no .NET source
is changed here. Relative finalized-journal path candidates in agent notes were not
reproduced and are not represented as fixed findings.

## Publication and evidence

Publication is based on `72aa51298` after fetch/rebase and source-hash
verification. Incoming changes since the initial base contain benchmark records
only. Each of the 16 changed engine files matches the final test manifest. Commits
are backed up on the private session branch during verification, then published
linearly to private main; the main checkout is fast-forwarded afterward.

Evidence is archived locally and on HP at
`~/archive/libhft/reviews/2026-10-09-codex-review-recovery-1009/`: original and final
sources, counterfactual drivers, full reports, sanitizer logs, independent reviews,
source hashes, verification summary, published patch and history bundle. Both
archive manifests are checked before this session's worktree, branch, scratch and
build outputs are removed. Other sessions' worktrees and the earlier .NET proposal
branch are preserved.
