fix(nomadnet): address Greploop round 1 on the cache transient-stall guard

Three findings on the transient-stall bail, all valid:

- Reload invalidation: a bail recorded BYPASS, but NomadNetCacheFlow::service()
  accepts only MISS as a successful invalidation, so a bail during an admitted
  reload reported 'Page cache invalidation failed' instead of falling through
  to a live fetch. The bail now records MISS.

- Open-resource leak: the bail cleared read_open_/write_open_ (and abandoned an
  open directory enumeration) without calling endRead()/abortWrite()/endList(),
  leaking SD handles. The bail now releases each in-flight resource via the
  seam's own teardown (bounded best-effort; a still-transient close is
  accepted rather than re-pinning the op).

- Tick-vs-time: the not-ready UNAVAILABLE path returns immediately (no bus
  wait), so a pure 500-tick budget could expire during a legitimate SD mount
  window and disable caching for the whole session. The bail is now gated on
  BOTH the tick floor AND a 10s wall-time window (service() takes a monotonic
  ms clock; production passes millis(), 0 is a safe default for tests).

Regression tests: reload-invalidation bail -> NEED_LIVE (flow), flat-clock does
not bail, healable transient keeps authority, list-open (RECOVERY_END) stall
bails and releases the handle (cache).
This commit is contained in:
Torlando committed 2026-09-15 06:27:14 +00:00
1 parent feb7f0574b
commit 32a09938d8
7 files changed
+137 -30

No files matched your search

+2 -2
View File
@@ -31,10 +31,10 @@ CacheFlowState NomadNetCacheFlow::begin(const CacheKey& key, std::uint64_t now,
return state_;
}
void NomadNetCacheFlow::service() {
void NomadNetCacheFlow::service(std::uint64_t now_ms) {
if (state_ == CacheFlowState::CANCELLED || state_ == CacheFlowState::FAILED)
return;
if (cache_.busy()) cache_.service();
if (cache_.busy()) cache_.service(now_ms);
if (state_ == CacheFlowState::INVALIDATE) {
if (cache_.busy()) return;
if (!invalidation_admitted_) {