# Unix metrics socket ownership in C++ and managed endpoints

The Linux C++ FIX and FIXP metrics endpoints now refuse a live listener instead
of deleting its name. Startup confirms a stale stream socket before replacing
it, and shutdown removes only its owned inode in the original directory. The
fix passes both editions and sanitizer checks without adding a session hot-path
lock. Independent Java and .NET probes expose replacement deletion on close;
those managed fixes and the unchanged non-Linux C++ branch remain outstanding.

## Reproduced C++ failures

At `62304f15e`, with only the first regression-test patch applied, five separate
probes exit 134 at their assertions: a second endpoint overwrites a live endpoint;
a foreign stream listener is overwritten, including with a full accept queue;
a datagram socket is overwritten; and an old endpoint's Stop removes a replacement
endpoint's name. The checks compare device/inode identity and, where the listener
is ours, verify that the preserved endpoint still serves a scrape. Two additional
baseline assertions establish the new bounded-directory-lease contract: the old
implementation ignores a foreign directory lock during both start and cleanup.
The stale-recovery and repeated-cycle control passes on the baseline.

The archive keeps all seven assertion failures, the successful control, the
exact test patch, executable, build command and full C++ input hashes. These
are real filesystem/socket tests; no production hook or mocked path operation
is involved. The later expanded tests also run in the ordinary endpoint suite.

## Linux ownership changes

`detail::MetricsUnixListenerOwner` takes a bounded flock on a fresh open
description of the actual parent directory during admission or cleanup. This
matches the directory-inode coordination used by Rust, rather than creating a
lock file or relying on one process's mutex. Admission and shutdown wait at most
100 ms for that lease, plus scheduling; a busy cleanup leaves a recoverable stale
node. These are cold lifecycle operations, not FIX traffic or scrape-request locks.

An existing name is examined without following symlinks. Only a socket node is
eligible for recovery, and only a nonblocking connect returning ECONNREFUSED
confirms a stale stream socket. Success, a full backlog, permission errors,
datagram sockets and other uncertainty refuse admission. A short hard-link
alias pins and probes that particular inode; recovery checks the original name
still refers to it before removal.

New listeners bind a short temporary name and start listening before publishing
the configured name with an exclusive hard link. Publication cannot overwrite
an existing node. O_PATH pins the filesystem inode through cleanup, preventing
identity reuse after external unlink; the retained directory descriptor keeps
cleanup independent of a changed working directory or renamed parent. The
temporary alias is removed after publication. On Stop, the worker is joined and
the listener closed before leased, identity-checked cleanup. The FIXP wrapper
uses the same implementation.

The configured socket path limit and long relative names are preserved. Linux
procfs and a local filesystem supporting directory flock and socket hard links
are required. Unsupported operations fail closed. The directory must be protected
from uncoordinated mutation: advisory coordination and an identity check are not
an atomic unlink guarantee against a hostile directory writer. Unexpected node-pin
or cleanup I/O failures can leave a stale temporary or published node; injected
filesystem failures are not covered by this run. No installed runtime is replaced.

## Verification

The complete FIX endpoint, FIXP endpoint and operations-profile binaries pass
under stock GCC 14 in development and open editions, and Clang 21's sanitized
open edition. Those are nine successful suite executions with zero skips;
address, undefined-behaviour and leak checks remain enabled. The FIX endpoint
has fourteen new ordinary-suite ownership calls, including repeated concurrent
starts, a separately executed contender, changed cwd, renamed directories,
regular files, symlinks, permission refusal, maximum-length relative names and
repeated stale-recovery cycles with handle-count checks. The existing four
slow-client deadline/shutdown cases also remain green.

Six additional public-API startup-failure probes pass in the two GCC editions.
A one-shot LD_PRELOAD shim refuses temporary bind, listen, or pthread_create.
Each case observes Start returning false, no configured node or extra open
handle, then a successful retry and scrape on the same endpoint. Each log records
exactly one injected failure. These are external syscall faults, not production
test hooks; they are not run in the sanitized configuration. Compiler logs and
public-source scans are clean. Full C++ input hashes remain unchanged during the
run. All builds and probes use host-cpu-slot leases and per-session outputs on
/home. No latency-sensitive NIC or round-trip measurement ran.

## Managed replacement deletion

Four standalone public-API probes compile current Java and .NET sources, start
their Unix metrics endpoint, remove its original name, and install either another
metrics socket or a regular file containing `keep`. Closing the original endpoint
removes all four replacements. Every baseline probe exits 1; no reflection,
native test hook, injected failure or scheduling race is needed.

Java runs on OpenJDK 17, with library/probe sources compiled for Java 8; .NET
runs on net8.0. Exact toolchain output and both source manifests are archived.
These focused probes run no official managed suite or native session, and do
not establish JNI/hftnet availability or NIC conformance. The normal Maven
compile and .NET ProjectReference builds keep outputs outside the worktree.

The raw .NET Socket control is important: even without libhft, closing an old
bound Socket after its pathname is replaced removes the replacement. Therefore
wrapping only libhft's File.Delete in an identity check cannot close this defect.
The binding and underlying Socket cleanup also need ownership-safe names.
The managed findings are posted to their owning sessions in WIP_AGENT_CLAIMS;
no production Java or .NET source is edited by this patch.

## Evidence and remaining scope

The integrated repeat at `f40dc4ea9` again passes all nine complete suite runs
and six startup-failure/retry cases. The managed repeat at the same checkpoint
again reproduces all four replacement deletions and the raw .NET Socket control.
`integrated/` and `managed-integrated/` retain the corresponding logs and source
manifests. The exact checkpoint is retained on
`s/codex-libhft-hp-unix-metrics-verified-20261004`; the later WIP-only commits
do not change these tested C++ sources.

`~/archive/libhft/cpp-metrics-unix-20261004/` contains `baseline/`, `fixed/`,
`final/` and `managed-baseline/`. `fixed/` is the first successful nine-suite
checkpoint; `final/` includes the startup faults and strengthened subprocess
timeout. `managed-baseline/` keeps the four red probes and raw Socket control.
The source patches, manifests, executable and toolchain logs distinguish the
tested tree from its pre-fix source pin. Portable drivers and probe sources are
tracked under `internal/docs/bench/evidence/cpp-metrics-unix-20261004/`.
The managed driver expects corrected exit 0 by default; the known red baseline
was run with LIBHFT_MANAGED_UNIX_EXPECT_EXIT=1. Per-runtime overrides
LIBHFT_MANAGED_UNIX_EXPECT_JAVA_EXIT and LIBHFT_MANAGED_UNIX_EXPECT_DOTNET_EXIT
allow the two implementations' outcomes to be checked separately; actual and
expected exits are always printed. These overrides do not turn a known red
probe into evidence of a corrected endpoint. The historical baseline driver
requires pre-fix production sources with its retained test patch applied.

## Independent .NET cleanup verification

The owning session's `b268b09fc` fixes the Linux .NET endpoint's binding and
cleanup. At `0429e656f`, a fresh normal .NET build passes both independent
public-API replacement probes: the socket and regular file remain after the
old endpoint closes. The raw .NET Socket control still removes its replacement,
confirming the fix avoids the runtime's bound-name cleanup rather than changing
that runtime. Both corresponding Java probes still exit 1 and delete their
replacements. The mixed run explicitly expects Java 1 and .NET 0.

`~/archive/libhft/cpp-metrics-unix-20261004/managed-closure/` keeps all five
probe logs, compiler/toolchain output and unchanged Java/.NET source manifests.
No production managed source is edited by this review session. This closes the
two reproduced Linux .NET cleanup cases, not non-Linux ownership, all startup
faults, an official managed suite or native/NIC conformance.

At that checkpoint, remaining work included Java ownership fixes and portable
C++ ownership repair, wider startup/filesystem fault coverage, non-Linux .NET
cleanup verification, and the broader review/NIC campaign.
This was not a full C++ correctness matrix,
cross-language simultaneous-bind test, latency result or completion of the
all-flavours review.

## Independent cleanup verification on 11 October

At `839a66db8`, the same four public-API replacement probes all pass with their
default expected exit 0: Java and Linux .NET preserve both replacement sockets
and regular files. The raw .NET Socket control still deletes its replacement.
The normal managed builds and unchanged Java/.NET source manifests are retained
under `~/archive/libhft/metrics-closure-20261011/`. This independently closes
the two earlier Java failures as well as rechecking the Linux .NET correction;
it is not a managed session/native-suite or NIC run.

Portable C++ ownership landed in `b4ecac6f3`. The complete
`test_fix_metrics_http_portable` suite passes in development, open and sanitized
open builds with the callback-lifetime correction on `551e2de07`. These Linux
runs exercise the portable implementation's POSIX branch, not its Windows
runtime branch. The owning Java session's Linux and Windows qualification is
retained separately in `RECORD_2026-10-05_java-replay-metrics.md`.

Wider startup/filesystem faults, non-Linux .NET cleanup and the broader
review/NIC campaign remain separate work. The independent callback-lifetime
finding and verification are in
`RECORD_2026-10-11_hp-cpp-metrics-callback.md`.
