# Exact outbound throttle accounting review

The C++, Java and .NET token buckets divide elapsed nanoseconds by a rounded-down
nanoseconds-per-token interval. This overstates credit when the configured rate
does not divide one billion. At 600,000 messages per second, the interval becomes
1,666 ns and one second earns 600,240 tokens instead of 600,000. The C++ protocol
consults this bucket when outbound throttling is enabled; Java and .NET expose
the corresponding standalone utility. This finding does not establish that
the pure managed sessions expose or wire the same outbound-throttling policy.

## Correction and compatibility

C++ and Java now retain fractional credit in billionths of a token. Refill first
checks whether elapsed time fills the remaining capacity, before multiplying
elapsed time by the rate. The bounded multiplication cannot overflow: even the
C++ maximum capacity multiplied by one billion fits in `uint64_t`, and the Java
maximum capacity multiplied by one billion fits in `long`. An ordered Java
forward interval whose subtraction exceeds `Long.MAX_VALUE` saturates safely.
The delay query uses ceiling division and includes retained fractional credit.

Two previous behaviors deliberately change. A rate above one billion earns the
appropriate number of tokens per nanosecond rather than refilling an arbitrary
burst after 1 ns. A full bucket discards surplus fractional credit rather than
banking it outside its capacity. A backward clock step clears partial credit and
reanchors the baseline; repeated identical timestamps preserve partial credit.
Reconfiguration resets credit. Admission remains owner-thread-only, without new
locks, allocations or arithmetic in `Admit()` / `admit()`.
Each bucket gains one eight-byte fractional-credit field. In the C++ layout it
is appended so existing admission-field offsets stay unchanged.

## Reproduction and verification

The baseline is `bd3699bb1`, which adds the coordination claim but changes no
production code. Its C++ header SHA-256 is
`930742667b70b91f4522ada65e317e2c40c383fcc75c912634fc7ba0f0d141d9`;
its Java utility SHA-256 is
`029a28a82f8f847ebb45d33ad4e9e2526ee2f0614ae3e5b5965a4ac9d2d54bc9`.
The failing-before runner compiled immutable copies of those production files
with the updated tests. C++ reported 26,366 failed checks and Java failed nine
of its 19 methods. The isolated .NET probe compiled the actual utility outside
the claimed tree and confirmed the same 600,240-token result.

Regressions cover non-divisor rates, fractional credit and exact delays, saturation,
backward clocks, repeated timestamps, reconfiguration, very large forward jumps,
and the largest supported rates. Each C++ and Java suite also checks 70,000
deterministic time/spend steps against an independent fixed-point credit oracle.
With the fixes, both 19-case suites passed, including the oracle checks. The C++
suite also passed Clang 21 ASan/UBSan with leak detection and the repository's
optimized GCC 14/LTO build. The C++ session reliability suite passed, including
outbound refusal without consuming a sequence number, admission across origination
APIs, and exempt administrative/replay traffic. The five-suite Maven run passed
141 tests with zero skips, failures or errors: the limiter (19), TX queue (9),
settings (103), pure-Java send batching (6) and command admission (4).

An optimized assembly comparison of an external C++ wrapper around `Admit()`
was byte-identical against the baseline header. This verifies that isolated
admission function for this compiler, not whole-session latency: refill and query
code changed, and the session object is larger. All four engine files passed
public-export scanning with zero findings. Functional checks used the cooperative
CPU leases and are not quiet-host comparative measurements.

`~/archive/libhft/rate-limiter-review-20261002/` retains the failing-before sources
and log, reproduction scripts, the passing logs, selected Maven reports, assembly
comparison, source hashes and patch. C++ and Java build outputs stayed under the
session build root. The standalone .NET probe's initial NuGet restore wrote
intermediates to private scratch because its output property was applied too late;
an early `Directory.Build.props` fixes this, and those initial intermediates were
moved to the build root. Its own idle compiler server was stopped after the run
so it could not retain the lease; subsequent probe commands disable shared compilation.

## Remaining scope

The .NET counterpart is requested but not edited: the repository restructuring
session reserves its source and test tree. Rust has no corresponding standalone
FIX rate utility. These are accounting regressions, not physical-NIC latency
measurements or a replacement tuned benchmark matrix.
