# Java replay, metrics endpoint and C++ split lifecycle review — 2026-10-05

Base engine revision: `c983076c7`. Session: `codex-review-java-replay-1005`.
All changes were developed in the session worktree. BEELINK2 builds and test
processes used a guarded shared CPU lease, process/memory preflight and external
`LIBHFT_BUILD_ROOT`; native builds used at most `-j8`, with `-flto=1`.
Parallel agents reviewed disjoint source scopes; only the root ran builds.

## Reproductions and fixes

Full QuickFIX administrative records were replayed as PossDup application data.
The session now uses an application-only entry point in the existing replay
encoder, checking message type during the existing parse. The generic public
encoder continues to support administrative rewriting. Both single-session and
TCP acceptor recovery gap-fill the administrative sequence numbers. The corrected
baseline suite has 56 failures and 24 passing marker/application controls across
both QuickFIX layouts, both transports, ResendRequest and tag 789 recovery.
The first version's stored SequenceReset matched its expected generated GapFill;
that fixture was corrected to store NewSeqNo=99, and all 80 cases were rerun
against the retained, unmodified baseline classes. The original 48-failure run
is retained separately and is not represented as 56 independent failures.

Sparse replay walked every integer in the requested sequence range. Built-in
memory, async-file, mmap and QuickFIX stores now offer an optional inclusive
next-record hint. A scalar prefix of at most the retained-index capacity keeps
nearby records cheap; a retained-key minimum scan skips larger gaps. This avoids
both an unbounded empty numeric scan and a full index scan at every small gap.
Custom implementations retain a constant-work scalar default. No save-path
metadata, allocation or ordered container is added. Both session loops preserve
failed-write early returns and reload the payload after any preceding GapFill.
The internal shared-buffer regression deliberately reads another stored key from
a wire observer; it is not a guarantee of arbitrary session reentrancy or a new
public observer/store-access API.

The metrics worker previously renewed TCP header timeouts on progress, gave
response writes a separate/unbounded budget, and left accepted clients open at
shutdown. Both transports now share one monotonic two-second deadline across
headers, elapsed render time and response bytes. Nonblocking I/O checks stop and
deadline between progress steps; shutdown closes the active channel. Arbitrary
application render code is still synchronous and must return promptly. A callback
that outlives the bounded join prevents restart until its old worker exits.
Interrupted callbacks terminate the worker, and self-close does not self-join.

Unix metrics cleanup previously deleted whatever occupied its configured name.
The endpoint binds a private name, captures that node's identity, publishes the
requested name with exclusive hard-link creation and removes the private name.
Cleanup checks the original identity without following links and pins the parent
with SecureDirectoryStream where available. Replacement files, sockets and links
survive; parent rename and symlink changes do not redirect cleanup. On Windows,
where Java basic file keys are null, a private UUID hard link retains a reference
for Files.isSameFile; normal close removes both names. Without a secure directory
stream, a renamed original directory cannot be located and its moved names are
left untouched. Hard-link support is required for publication. If reference
creation fails before identity is established, conservative failed-start cleanup
can leave an unpublished socket. Bind spelling is preserved for Unix pathname
limits, relative paths and symlink traversal semantics.

C++ callers using ReadHotOrEof followed directly by ProcessStepEof could observe
protocol LoggedOn while provider lifecycle remained Connected until the next
timer scan. ProcessStepEof now performs the lifecycle update itself; ServeStep
and StepOnce no longer duplicate it. The old-header loopback probe reproduced
Connected after Logon without reaching its timeout. Commit `55c52df96` publishes
this change; three development suites, one open suite and one ASan/UBSan open
suite pass. No extra comparison is added to the normal wrapper path.

## Verification

The full JNI-required development suite passes 3,226 unique tests, and the full
open suite passes 2,784. Both have zero failures/errors and zero skipped cases in
the final XML reports. Each suite's ordinary fork initially skips the three
counters-disabled tests; all three then pass in their dedicated configured fork.
Those intentional first-fork skips are not missing native coverage.

All 97 new replay cases pass in development; open includes the 88 cases that do
not require pro stores. Boundary/overwrite/reset/reopen checks, the scalar custom
store fallback and invalid hints, ordinary encoder admin support, both replay
loops and existing Logon-recovery write-failure tests are covered. Independent
reviewers checked overflow, store eligibility and wrapper/callback behavior.

The full suites use the source snapshot before the last metrics relative-path
and Windows fallback refinements. Only the endpoint and its ownership test differ
from that snapshot. Final Java-8-API compilation and focused Linux/Windows runs
validate those refinements separately. The relative-path regression fails before
the correction and passes afterward. The real Windows JDK 21 probe confirms
null basic file keys, socket hard links, traffic through the published hard link,
and native same-file equality/inequality across socket replacement.

Final focused metrics validation passes all 30 cases on Linux with no skips.
Windows passes 25 cases; five symlink fixtures explicitly skip because this
account lacks the creation privilege. Only fixture creation is assumption-guarded;
endpoint errors continue to fail tests. The first Windows run retained those
five fixture failures before the platform precondition was made explicit.
Export scan covers the 13 changed Java files with zero findings; whitespace
checks pass. The five verified C++ suite runs are described above.

## Replay timing

The transcript driver seeds public QuickFIX stores, fixes the transcript clock,
and times only injectRawFix for the ResendRequest. Draining output and computing
SHA-256 are outside the timed interval. Every compared case retains identical
full-wire SHA-256, frame counts and byte counts. The four-frame ten-million-key
request emits 655 bytes, with SHA-256
`605c1653dc5d19545228ee46059a7cafef24f8251615c9abd31ffefe8e58af2b`.

Longer warmed runs use 500 warmups and 501 timed observations per case, in
baseline/candidate/candidate/baseline process order. The ten-million-key request
has baseline medians 10.61–10.67 ms and candidate medians 5.42–6.15 us across the
two layouts and both repeats. The hundred-thousand-key case falls from
108–121 us to 5.32–6.34 us. Dense, alternating and stride-17/257/1025 controls are
retained to expose index-scan overhead. First-process small-range timing varies;
these measurements demonstrate the large-gap improvement, not a general
throughput or tail-latency claim. The complete process medians, including noisy
first comparisons, are retained rather than selecting the fastest observations.

An additional alternating-only control uses 10,000 warmups and 1,001 timed
observations, again in baseline/candidate/candidate/baseline order. QuickFIX-C++
baseline medians are 95.5/103.1 us versus 96.5/98.1 us for the candidate;
QuickFIX/J gives 93.4/92.0 us versus 92.3/93.2 us. This longer warmup resolves
the first-pair alternating slowdown; identical wire hashes are retained.

Raw logs, source manifests and reusable probes are sealed under
`~/archive/libhft/reviews/2026-10-05-codex-review-java-replay-1005/`.
Windows validation covers only the focused pure-Java metrics endpoint and JDK
identity controls; it does not qualify Windows JNI sessions, NIC offload or
external FIX-engine sessions.
