mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-27 18:08:13 +00:00
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).
114 lines
3.9 KiB
C++
114 lines
3.9 KiB
C++
// Copyright (c) 2026 Pyxis contributors
|
|
// SPDX-License-Identifier: MIT
|
|
#include "NomadNetCacheFlow.h"
|
|
|
|
namespace UI { namespace LXMF { namespace NomadNet {
|
|
|
|
CacheFlowState NomadNetCacheFlow::begin(const CacheKey& key, std::uint64_t now,
|
|
bool reload) {
|
|
cancel();
|
|
key_ = key;
|
|
now_ = now;
|
|
ExternalVector<std::uint8_t>().swap(page_);
|
|
status_ = reload ? "Invalidating cached page..." : "Checking page cache...";
|
|
lookup_admitted_ = false;
|
|
invalidation_admitted_ = false;
|
|
if (reload) {
|
|
state_ = CacheFlowState::INVALIDATE;
|
|
if (!cache_.busy()) {
|
|
invalidation_admitted_ = cache_.invalidate(key_) == CacheResult::PENDING;
|
|
}
|
|
return state_;
|
|
}
|
|
state_ = CacheFlowState::LOOKUP;
|
|
if (!cache_.busy()) {
|
|
lookup_admitted_ = cache_.beginLookup(key_, now, false) == CacheResult::PENDING;
|
|
if (!lookup_admitted_) {
|
|
state_ = CacheFlowState::NEED_LIVE;
|
|
status_ = "Requesting page...";
|
|
}
|
|
}
|
|
return state_;
|
|
}
|
|
|
|
void NomadNetCacheFlow::service(std::uint64_t now_ms) {
|
|
if (state_ == CacheFlowState::CANCELLED || state_ == CacheFlowState::FAILED)
|
|
return;
|
|
if (cache_.busy()) cache_.service(now_ms);
|
|
if (state_ == CacheFlowState::INVALIDATE) {
|
|
if (cache_.busy()) return;
|
|
if (!invalidation_admitted_) {
|
|
invalidation_admitted_ = cache_.invalidate(key_) == CacheResult::PENDING;
|
|
return;
|
|
}
|
|
if (cache_.lastResult() == CacheResult::MISS) {
|
|
state_ = CacheFlowState::NEED_LIVE;
|
|
status_ = "Requesting page...";
|
|
} else {
|
|
state_ = CacheFlowState::FAILED;
|
|
status_ = "Page cache invalidation failed";
|
|
}
|
|
return;
|
|
}
|
|
if (state_ != CacheFlowState::LOOKUP || cache_.busy()) return;
|
|
if (!lookup_admitted_) {
|
|
lookup_admitted_ = cache_.beginLookup(key_, now_, false) == CacheResult::PENDING;
|
|
if (!lookup_admitted_) {
|
|
state_ = CacheFlowState::NEED_LIVE;
|
|
status_ = "Requesting page...";
|
|
}
|
|
return;
|
|
}
|
|
if (cache_.lastResult() == CacheResult::HIT && cache_.takeBody(page_)) {
|
|
state_ = CacheFlowState::READY;
|
|
status_ = "Cached page; current reachability not checked";
|
|
} else {
|
|
state_ = CacheFlowState::NEED_LIVE;
|
|
status_ = "Requesting page...";
|
|
}
|
|
}
|
|
|
|
bool NomadNetCacheFlow::acceptLive(const std::vector<std::uint8_t>& body,
|
|
const CacheEligibility& eligibility,
|
|
std::uint64_t now) {
|
|
if (state_ != CacheFlowState::NEED_LIVE || !eligibility.successful ||
|
|
!eligibility.valid || eligibility.partial || eligibility.malformed ||
|
|
eligibility.truncated || eligibility.error || body.empty()) {
|
|
state_ = CacheFlowState::FAILED;
|
|
status_ = "Malformed NomadNet response";
|
|
return false;
|
|
}
|
|
try {
|
|
page_.assign(body.begin(), body.end());
|
|
} catch (const std::bad_alloc&) {
|
|
state_ = CacheFlowState::FAILED;
|
|
status_ = "Page is too large for available memory";
|
|
return false;
|
|
}
|
|
state_ = CacheFlowState::READY;
|
|
status_ = "Page loaded (live)";
|
|
if (cache_eligible(eligibility)) {
|
|
const auto directive = parse_cache_directive(body.data(), body.size());
|
|
if (directive.valid && directive.ttl)
|
|
cache_.beginCommit(key_, body, now, directive.ttl);
|
|
else
|
|
cache_.invalidate(key_);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NomadNetCacheFlow::cancel() {
|
|
if (cache_.busy()) cache_.cancel();
|
|
ExternalVector<std::uint8_t>().swap(page_);
|
|
if (state_ != CacheFlowState::IDLE) state_ = CacheFlowState::CANCELLED;
|
|
status_.clear();
|
|
}
|
|
|
|
bool NomadNetCacheFlow::takePage(ExternalVector<std::uint8_t>& output) {
|
|
if (!pageReady()) return false;
|
|
output.swap(page_);
|
|
return true;
|
|
}
|
|
|
|
}}} // namespace UI::LXMF::NomadNet
|