Repository navigation
fix(nomadnet): bound cache transient-stall so a failing SD seam cannot freeze the UI - #97
Merged
Merged
Conversation
…t freeze the UI The SD page cache (16af3b5) made the SD card a soft dependency of NomadNet page loads, but its step machine retries a transient storage result (BUSY = SPI-mutex timeout, UNAVAILABLE = card not mounted) forever with no budget. On a persistently unhealthy seam the boot-time recovery pins operation_ != NONE, the flow stays LOOKUP, and the UI freezes at 'Checking SD page cache...' (the CACHE state has no deadline, unlike every other NomadNet state). NomadNetCache::service() now compares each call's entry state to the previous call's. Any advance (op, offset, scan/cleanup index, scan count, open-flags) resets a stall counter, so slow-but-progressing steps (chunked 64 KB transfers, up to 96-record directory scans) never false-trip; a no-progress tick is a transient stall. Past 500 consecutive no-progress ticks (far beyond any real SPI contention or SD mount window) the cache bails: mark the namespace non-authoritative for the session (lookups/commits bypass) and clear the op, so the flow falls through to a live fetch -- the pre-cache page-load behavior. Regression tests: cache-level (persistent UNAVAILABLE recovery bails; lookup bypasses after bail) and flow-level (a permanently BUSY beginList no longer parks the flow in LOOKUP; it reaches NEED_LIVE in bounded ticks). Both fail on the pre-fix code and pass with it. Verified: tdeck firmware build SUCCESS; tests/native 121 passed (3 pre-existing env failures, fail identically on origin/main baseline); tests/build_scripts 184 passed.
Contributor
|
…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).
…millis() A stall spanning the 32-bit millis() rollover zero-extends to a value smaller than the pre-wrap start, so the now_ms >= start_ms guard blocked the bail until the counter lapped the ~49.7-day start value. Use 32-bit unsigned subtraction, which measures true elapsed time across the wrap. Regression: a stall starting near the counter max and wrapping bails in bounded ticks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the NomadNet UI freeze where opening any page hung forever on "Checking SD page cache...".
The SD page cache (
16af3b5, Aug 17) made the SD card a soft dependency of pageloads for the first time.
NomadNetCacheretries transient SD storage results(
BUSY= SPI-mutex timeout,UNAVAILABLE= card not mounted) with no budget ordeadline: every step treats them as "retry next tick" forever. Because
NomadNetCacheFlow::service()early-returns whilecache_.busy()and the UI'sNomadState::CACHEhas no timeout, a persistently-unhealthy SD seam pins the UIin "Checking SD page cache..." indefinitely.
Fix
NomadNetCache::service()now detects no-progress ticks across consecutivecalls: if this call's entry state (op, offset, scan/cleanup index, scan count,
open-flags) is identical to the previous call's, a counter increments; any real
advance resets it. After
MAX_TRANSIENT_STALL_TICKS(500 — far beyond any realSPI contention or mount window, and safe for legitimate multi-tick steps like
64 KB chunked commits and 96-record recovery scans) it bails:
operation_ = NONE), andrecovery_complete_ = false), so subsequentbeginLookup()calls returnBYPASS.The flow then drops to
NEED_LIVEand does a live fetch — the pre-cachebehavior. No data is wiped; a healthy SD later in the session simply means no
cache benefit for that boot. The cache becomes a no-op rather than a freeze.
Verification
test_nomadnet_cache.cpp: persistentUNAVAILABLEat boot and persistentBUSYat boot both bail in bounded ticks and mark the namespacenon-authoritative; a mid-transfer commit stall bails and reports.
test_nomadnet_cache_flow.cpp: drives the realNomadNetCacheFlowwith aBusyListStoragemock — asserts the flow reachesNEED_LIVE(not stuck inLOOKUP) in bounded service ticks (the exact UI symptom).tdeckfirmware: build SUCCESS (23.2% RAM, 92.9% flash).tests/native: 121 passed;tests/build_scripts: 184 passed. (3 pre-existingenv failures —
nomadnet_x86_flow, 2lvgl_dependency— fail identically onthe origin/main baseline, not regressions.)
0.3.8-39-gfeb7f05, all partitionspreserved (NVS/LittleFS/identity/otadata/coredump), app0 read-back verified
sha256. Device boots clean; a NomadNet page loads (first request needed a
second attempt — live-fetch fallback is working as designed).
Test Plan
pio run -e tdeckbuilds cleanpytest tests/native/test_nomadnet_cache.py test_nomadnet_cache_flow.py test_nomadnet_storage.pypass (RED on main, GREEN with fix)Closes the NomadNet page-cache UI freeze.