Repository navigation
fix(core): abandon a provider request that never settles - #8
danielbanariba wants to merge 1 commit into
Conversation
state.queue is a promise chain, so a request that neither resolves nor rejects blocks every later refresh for that provider. A socket left dangling by a suspend outlives the resume, and the widget is stuck until the shell is restarted. The request cannot be given a deadline where it runs: Qt's QML XMLHttpRequest implements neither timeout nor ontimeout, and the QML JS engine has no setTimeout (verified on Qt 6.11.2 -- only abort() exists). The poll is the only clock available, so a request still in flight when the next attempt arrives, and older than DEFAULT_STALLED_REQUEST_MS, is abandoned and the chain restarted. Only the newest request clears inFlight, so an abandoned one that finally settles cannot clear the flag out from under its successor.
|
Interaction with #4, which I found after opening this: that PR also touches More substantively, #4 raises
Still correct, just slower — 120s remains a safe floor because it only has to exceed a healthy request's duration. If you merge #4 first I am happy to express this relative to Worth saying that #4 independently corroborates the failure this PR addresses: the 429 with retry-after ~340s it describes is exactly the condition that leaves a request outstanding long enough to matter. |
Closes #7
Summary
DEFAULT_STALLED_REQUEST_MS, is abandoned:state.queueis reset so the chain can advance.inFlightis now cleared only by the newestrequestId, so an abandoned request that eventually settles cannot clear the flag out from under its successor.No new timer is introduced. The poll is already the clock, and the whole check is
Date.now()arithmetic, so it holds for the GNOME (Soup) runtime as well as QML.Independent of #5 — this branch is cut from
mainand applies on its own.Changes
shared/core/scheduler.jsDEFAULT_STALLED_REQUEST_MS;nowMs/stalledRequestMsinjectable alongside the existingsetIntervalFn; the abandonment check at the top ofqueueProviderRequest;inFlightSinceMsstamped when a request starts and cleared only by the newest requestshared/core/state.jscreateProviderStategainsinFlightSinceMs: nulltest/unit/scheduler.test.jsabandons a request that never settles instead of stalling the queueWhy not a request timeout
That was the first thing I tried. It is not available in this runtime — verified on Qt 6.11.2 rather than assumed:
xhr.timeoutxhr.ontimeoutsetTimeout()in the QML JS enginexhr.abort()So a deadline would have to be built from a QML
Timerand injected down intoruntime.js, which is a.pragma librarywith no access to QML types. Abandoning the chain achieves the recovery with none of that plumbing. It does leak the dangling request until Qt gives up on the socket, which seemed the better trade than a cross-layer timer — happy to do theabort()version instead if you would rather reclaim it.Test plan
getUsage()returns a promise that never settles, then a second attempt inside the grace period is correctly left queued (callsstays at 1), and a third past the threshold abandons it and reaches the provider (callsbecomes 2). It then asserts the fresh result actually lands in the summary.event loop emptied with an unsettled top-level await). That is a mechanical demonstration of the deadlock rather than an assertion about it. With the block restored, exit0.bunwas not available in my environment, so the suite ran on Node 26 through a small localbun:testshim backed bynode:testmock timers. No test file or import was modified to accommodate it, and the shim is not part of this PR. Worth re-running underbun testbefore merge.Note on the threshold
DEFAULT_STALLED_REQUEST_MSis 120s against a 180s poll, so a request gets a full grace period and is only abandoned once a subsequent poll finds it still hanging. If you would rather express it relative topollIntervalMsthan as its own constant, say the word.