Mobile: the app can freeze permanently while idle-syncing, apparently after RPC rate-limiting
- Langage dominant
- TypeScript
- Étoiles
- 5
- Forks
- 28
- Merge moyen
- 9 h 50 min
- PR mergées (30 j)
- 123
Description
## Symptom
An idle mobile wallet stops responding entirely and never recovers. The JS main thread stops, so the UI is frozen, no console output is produced, and no state can be read. Only force-quitting the app would clear it.
Seen three times out of three in one CI job, but this is a **product** report rather than a test one — the wallet was idle on the home screen doing nothing but its 3s auto-sync, which is what a phone in a pocket does.
## Evidence
Run [32905044427](https://github.com/0xMiden/wallet/actions/runs/32905044427), job `Mobile E2E (devnet)`, spec `mint-and-balance.ios.spec.ts`. Each of the three attempts failed at `claim_wallet_b`, and in each one the second wallet — idle for the preceding few minutes while the spec worked on the first — went permanently silent:
| attempt | last HTTP 429 burst | last console output | test gave up | silence |
| --- | --- | --- | --- | --- |
| 1 | 22:42:49 | 22:44:36 | 22:47:04 | 2.5 min |
| 2 | 22:53:07.164-.175 | 22:53:07.776 | 22:56:41 | 3.5 min |
| 3 | 23:03:07.360-.362 | 23:03:07.435 | 23:06:01 | 2.9 min |
Three independent signals that this is a stopped main thread rather than a slow one:
1. **Console silence.** These are `Console.messageAdded` events pushed by the app, not something the harness polls, and the app logs continuously (thousands of lines per attempt) right up to the cutoff.
2. **Nothing drains.** Outstanding CDP `Runtime.evaluate` calls against the frozen app peaked at 734 / 1319 / 1047 and *all* of them were still unanswered at teardown. A queue of trivial global reads drains in milliseconds once the thread frees, so the thread never freed. The sibling wallet in the same attempts peaked at 7 / 80 / 155.
3. **No state readable.** `report.json`'s `stateAtFailure` has a complete snapshot for the healthy wallet and, for the frozen one, an empty `currentUrl` and no wallet state at all.
## Why a stall is fatal here specifically
Mobile runs the WASM client on the main thread — `WebClient: Web Worker shim disabled by caller (useWorker=false)` in the logs — so a single SDK call that never returns takes the whole UI with it, not just the sync.
This is the same shape as #718, which was fixed for the offscreen realm in #766: an await on a gRPC call carrying no deadline of its own, parked forever while holding the WASM mutex. That fix bounded the delegated prove paths (`withDelegatedProveTimeout`, `remoteProver`). The wallet here was not proving — it was idle, so the suspect is the sync path, which has no equivalent bound.
## Suspected trigger, not established
In attempts 2 and 3 the very last thing the app logged before dying was a burst of three HTTP 429s from the RPC, in the same 20ms window. Suggestive, but not sufficient on its own: the previous **passing** run of the same job took 70 429s across its specs without freezing. So a 429 alone does not do it — more likely a race, or a specific call that mishandles the rejection while holding the client lock.
## Suggested direction
1. Bound the mobile sync the way #766 bounded the delegated proves, so no single SDK call can park the main thread indefinitely.
2. Confirm the trigger by driving 429s at an idle mobile wallet deliberately, rather than inferring it from three CI samples.
## Not this
The harness amplified this into a confusing failure by queueing ~1000 screen-capture reads against the frozen WebView, which surfaced as a generic `WebView/RWI wedged` timeout. That part is #776 and is unrelated to the defect above. The job itself re-ran green with no code change, so nothing is currently red on account of this.
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.