Skip to content

fix(firmware): pace + recover multi-MB flash writes that wedge the RPC session - #105

Merged
millsmillsymills merged 12 commits into
phase1from
fix/firmware-session-health
Jun 10, 2026
Merged

fix(firmware): pace + recover multi-MB flash writes that wedge the RPC session#105
millsmillsymills merged 12 commits into
phase1from
fix/firmware-session-health

Conversation

@millsmillsymills

@millsmillsymills millsmillsymills commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Substantial work toward #99 (does not close it). Root-caused and characterized against live hardware (Momentum mntm-012, USB).

Root cause (primary fix)

_storage_write_internal sent every has_next fragment back-to-back and read a response only on the final fragment. The device reassembles fragments and acks only the last, so an unpaced multi-MB write floods USB CDC (~1.5 MB/s) far faster than the device drains to SD (~80 KB/s). Past ~1 MB the device's RPC input overruns and wedges mid-write, and the leftover bytes break the next start_rpc_session negotiation (the unrecoverable "hard wedge").

Fix: pace each non-final fragment to the device's sustainable throughput (_WRITE_THROUGHPUT_BYTES_S), applying host-side backpressure. The existing per-byte storage_write timeout already budgets ~2× the paced send time, so no spurious timeouts.

Recovery (backstop)

For a soft wedge that still slips through, install_bundle takes a resync hook (transport reconnect, wired from the firmware tool's FlipperClient). _push_file verifies each file's md5 and, on any non-matching digest, reconnects and re-verifies the already-written data without rewriting (a fresh write just re-wedges). A wedged session returns garbage digests, so only a digest that keeps disagreeing after a clean reconnect is a real corrupt-transfer mismatch (fail closed); a persistently unreadable digest is an unrecoverable wedge (fail closed). _verify_md5_check_md5 returns match/mismatch/unreadable to drive this.

Validation

  • Unit (210 pass): fragment pacing (one sleep per non-final fragment, none for single-frame); write/verify/reboot happy path; target/manifest aborts; resync recovery (verify-after-reconnect, no rewrite); persistent-mismatch and no-resync fail-closed.
  • Live: soft-wedge recovery confirmed for writes up to ~1.2 MB; the unpaced hard wedge reproduced and traced to the fragment flood above.

Needs on-device re-validation (device power-cycle-pending after diagnostics)

Confirm a paced ~1.5–2 MB write no longer wedges, then run the end-to-end official↔Momentum flash + official golden capture (the remaining #99 items).

🤖 Generated with Claude Code

@millsmillsymills millsmillsymills changed the title fix(firmware): fail closed when RPC session wedges mid-flash fix(firmware): recover a wedged RPC session during multi-MB flash writes Jun 9, 2026
@millsmillsymills millsmillsymills changed the title fix(firmware): recover a wedged RPC session during multi-MB flash writes fix(firmware): pace + recover multi-MB flash writes that wedge the RPC session Jun 9, 2026
millsmillsymills and others added 10 commits June 9, 2026 21:09
Aggressive back-to-back multi-MB storage_write calls can wedge the device's
RPC session; install_bundle would keep going and reboot into the updater with
a half-written package.

Pace each file with a short settle and probe the session with a cheap ping
after the per-file md5 verify. If the session is unresponsive, abort before
the reboot with a power-cycle/DFU recovery hint instead of leaving a
half-applied update.

Partial work toward #99 (session-health check + inter-file pacing). On-device
end-to-end validation, host-side drain tuning, and the official golden capture
still require a real Flipper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live USB validation on Momentum mntm-012 reproduced the session wedge at a
1.0 MB write and showed it clears on a transport reconnect (new RPC session),
not the physical power-cycle the issue assumed. Point the operator at retry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A multi-MB storage_write transfers its data but can wedge the RPC session, so
the post-write digest reads back as None or garbage. Live USB validation on
Momentum mntm-012 showed the wedge clears on a transport reconnect, after which
the already-written data verifies - re-writing the file just re-wedges.

install_bundle now takes a resync hook (a transport reconnect, wired from the
firmware tool's FlipperClient). _push_file verifies each file's md5, and on any
non-matching digest reconnects and re-verifies the existing data without
rewriting; only a digest that keeps disagreeing after a clean reconnect is a
real corrupt-transfer mismatch. _verify_md5 became _check_md5 returning a
match/mismatch/unreadable status to drive this.

Validated live for writes up to ~1.2 MB (recovers cleanly). Larger writes can
drive the device into a harder wedge where start_rpc_session negotiation itself
fails and only a physical power-cycle recovers - documented on #99; no host-side
recovery is possible for that state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the multi-MB firmware-flash wedge: _storage_write_internal sent
every has_next fragment back-to-back and only read a response on the final one.
The device reassembles fragments and acks only the last, so an unpaced write
floods USB CDC (~1.5 MB/s) far faster than the device drains to SD (~80 KB/s).
Past ~1 MB the overrun wedges the RPC input mid-write and leaves stale bytes
that break the next start_rpc_session negotiation (the hard wedge).

Pace each non-final fragment send to the device's sustainable throughput
(_WRITE_THROUGHPUT_BYTES_S) to apply backpressure. The existing per-byte
storage_write timeout already budgets more than the paced send time, so no
spurious timeouts. Needs on-device re-validation against a full firmware image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Error

Live Momentum->Official flash: the full bundle pushed and verified, but the RPC
system_update issued right after the 1.58 MB resources write returned
UnspecifiedError, while the CLI `update install` of the same staged bundle
succeeded moments later. The trigger fired before the device finished settling
from the final large write.

_trigger_update now settles before the update trigger and, on an UnspecifiedError
(only), reconnects to a clean session and retries; a specific rejection
(target/manifest/integrity) still fails immediately, and a dropped link is still
surfaced. The CLI path confirmed the bundle itself is valid and installable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A single storage_write command has no intra-command flow control, so over a
very large file (the 11 MB Momentum resources blob) an SD-write stall lets a
backlog build until the device's RX buffer overruns and the write wedges
mid-transfer - even with even per-fragment pacing.

Send in bounded 128 KB bursts and settle 1 s after each so any backlog fully
drains before the next burst. Live-validated: an 11 MB write that previously
failed mid-transfer now completes (final ack received); the post-write soft
wedge that remains is recovered by install_bundle's resync-resume. The write
timeout now accounts for the per-burst settle overhead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For an 11 MB write the prior _push_file rewrote the file on every retry (3x
4.4 min) and re-verified immediately, hitting the device's RPC negotiation
cooldown before it had settled - so recovery never converged.

A storage_write that returns True means the data is durably on the SD card, so
the file is no longer rewritten on a post-write wedge; only a write that itself
fails is re-sent. After a resync, settle past the negotiation cooldown before
re-verifying so the fresh session's first call succeeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the "11 MB flash wedge": _send_rpc_message hardcoded a 2.5 s
response wait, but the device hashes the whole file before replying to
storage_md5sum - an 11 MB blob takes 60-120 s. The verify read timed out and
returned None, which install_bundle treated as a wedge and "recovered" with
reconnect churn that actually wedged the session.

The write and persistence were never the problem: live testing shows an 11 MB
file is written, stat-confirmed on disk, and md5-verified once the verify is
given time. _send_rpc_message takes a response_timeout; storage_md5sum uses a
generous 180 s so large-file verification completes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fix)

The 11 MB write "wedge" was a too-short storage_md5sum response timeout, not a
transfer overrun: with that fixed, even per-fragment pacing transfers and
verifies an 11 MB file (write True, stat-confirmed on disk, md5 matches). The
burst-and-settle drain windows added latency without a demonstrated benefit and
correlated with the verify stalling, so remove them and keep plain pacing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
storage_md5sum hashes the whole file on-device: for the 11 MB Momentum resources
blob that takes 60-120 s, is variable, and wedges the RPC session, which made the
per-file verify grind for 15 min+ and fail. storage_stat returns the size
immediately and never wedges.

_push_file now verifies files above _MD5_VERIFY_MAX_BYTES (2 MB) by on-device
size and keeps md5 for smaller files; the device's own update validation enforces
cryptographic integrity when the bundle is applied. Live: a _push_file of an
11 MB file now completes in ~3 min (was a 15 min+ grind) with the session healthy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@millsmillsymills
millsmillsymills force-pushed the fix/firmware-session-health branch from 615b740 to 0109bde Compare June 10, 2026 04:09
Replace the capture device's hardware_name in the unit-test RPC mocks
with a neutral placeholder so the test corpus carries no real device
identifier.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant