Skip to content

uci_wait_idle unbounded spin: phantom-socket failure manifests as 600s hang instead of clean error #37

Description

@JC-000

Symptom

uci_wait_idle spins indefinitely on the lda UCI_STATUS + fence loop with no retry budget. When upstream code feeds garbage into the UCI command pipe (e.g., the phantom-socket bug — see companion issue #36), the C64 spends hundreds of seconds and hundreds of thousands of register accesses waiting for a state change that never comes, instead of failing cleanly with a wall-clock-bounded timeout.

Discovery context

Surfaced during validation of c64-test-harness issue #90 (JC-000/c64-test-harness#90). Test tools/uci/test_https_local.py reaches the 600 s sentinel timeout because the C64 spends 605 s and 227k UCI register accesses (≈99% of total bus traffic) in this single PC range.

Full forensic analysis: JC-000/c64-test-harness#90 (comment)

Specific code

src/net/uci/uci_cmd.s:69-76uci_wait_idle. The unbounded design is intentional per the project's design notes, but for callers driven by host-side DMA/monitor (where wall-clock failure is preferable to indefinite hang) an opt-in bounded variant is appropriate.

DebugCapture trace from a wedged run (/tmp/uci_https_debug/20260510_104617/tail.txt) shows >350k hits each at PCs $2307–$230B — every single one is the lda UCI_STATUS + fence sequence. Interspersed are lone $DF1D writes every ~2k cycles when net_tcp_send's retry path issues a CMD_DATA push. The spin re-enters immediately after each write.

Suggested fix

Two viable shapes:

Option A: opt-in bounded variant (preferred)

Keep the existing unbounded uci_wait_idle for paths where indefinite spin is correct (boot, init). Add a sibling uci_wait_idle_bounded(timeout_jiffies) that returns CARRY-set on timeout. Migrate network-IO retry paths (net_tcp_send, net_tcp_recv, tls_*) to the bounded variant. Bound budget can be a build-time constant (e.g., 60 jiffies = ~1 s).

Option B: global hard cap

Add a single global cycle-counter check inside uci_wait_idle that gates the spin on a configurable upper bound (e.g., 5–10 s wall-clock-equivalent). Smaller diff; less surgical. Risk: changes semantics for paths that legitimately want unbounded spin.

Option A is cleaner — preserves existing behavior, adds a new tool, callers opt in.

Severity

Medium alone — symptomless without an upstream fault. But paired with the phantom-socket bug in #36, this bug is what converts a transient firmware fault into a 600 s test hang. Bounded spin would surface the underlying failure within seconds, making CI feedback and bisection dramatically faster.

Verification

After fix: tools/uci/test_https_local.py should fail in <2 s with a clean error pointing at net_tcp_send (or wherever the bounded uci_wait_idle returns CARRY-set), instead of hanging for 600 s.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions