fix: escalate to an incremental ReconnectApp when the missing-updates fetch stops advancing the client#95477
Conversation
… fetch stops advancing the client Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mountiny
left a comment
There was a problem hiding this comment.
This looks sound to me and the comments are self explanatory, thank you!
Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com>
|
@codex review |
|
@Krishna2323 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
I'll be reviewing this as continuation of #94974 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a68e3353fc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ranches Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@MelvinBot review |
Review — looks solid ✅The approach is sound and the backend-behavior claims that the whole fix rests on check out against the code:
Test coverage is comprehensive (escalate, don't-escalate-on-response-progress, don't-escalate-on-advance-mid-flight, don't-escalate/don't-latch-on-failure, pending-updates branch, single-unpause). I could not run the suite in this sandbox (jest execution is blocked here), so I relied on static tracing plus the author's stated 19/19 pass — worth confirming CI is green. Two things to confirm (not blockers)1. Guard release depends entirely on the client advancing — what backstops a failed escalation reconnect?
2. Primary stall check assumes GetMissingOnyxMessages responses always carry
|
There was a problem hiding this comment.
Pull request overview
This PR adds a “stall guard” to OnyxUpdateManager so that when GetMissingOnyxMessages repeatedly returns a successful (200) response that does not advance the client’s applied update ID, the client escalates recovery to a single incremental ReconnectApp and then backs off further missing-update fetch attempts until the client advances. This aims to prevent a ~1 request/second fetch loop and keep the SequentialQueue from freezing under high-traffic update bursts.
Changes:
- Add stall detection to missing-update fetches and escalate to incremental
ReconnectAppwhen a 200 response doesn’t advance the client. - Share the stall/backoff guard across both the gap-detection branch and the pending-updates branch.
- Add unit tests and mock plumbing to cover stall escalation, backoff behavior, and queue resume invariants; document the guard in
SEQUENTIAL_QUEUE.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/unit/OnyxUpdateManagerTest.ts | Adds new unit tests validating stall escalation/backoff and SequentialQueue resume behavior. |
| src/libs/actions/OnyxUpdateManager/index.ts | Implements stall detection, escalation to incremental reconnectApp, and backoff gating shared by both fetch call sites. |
| src/libs/actions/mocks/App.ts | Extends the App action mock to support returning a “no progress” GMOM response and to observe reconnectApp calls in tests. |
| contributingGuides/SEQUENTIAL_QUEUE.md | Documents the new stall guard behavior and its interaction with SequentialQueue deduping and recovery. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Restores the exact pre-refactor request value instead of the Number() coercion, matching what the gap branch already passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Answering the two confirm-items from #95477 (comment): 1. What backstops a failed escalation reconnect? The escalated 2. |
…e client One useless answer only proves the server could not serve the range at that moment, so the guard no longer treats it as permanent. After escalating, fetches from the same client state are skipped for 60 seconds; the first Pusher event past that mark retries the fetch, and a repeat stall escalates (and alerts) again. There is no timer machinery: the guard stores when it latched and the check runs on the next event. This closes the hole where the single escalated ReconnectApp could be lost (the SequentialQueue gives up on a request after too many failed retries, or the request is answered with an error jsonCode) and the client would then silently stop syncing until an external reconnect. A truly stuck client now also stays visible as one alert per minute instead of a single alert that reads the same as a recovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@mkhutornyi addressed everything |
|
@mkhutornyi lets prioritize this one please! thanks |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppweb-android.movAndroid: mWeb ChromeiOS: HybridAppios-web.moviOS: mWeb SafariMacOS: Chrome / Safariweb-android.mov |
Review — LGTM ✅Re-traced the stall guard against the current branch ( Key invariants re-verified against the code: Details
One non-blocking nit: Nice work — the shared guard, the response-first stall read, and the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9543283773
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
mkhutornyi
left a comment
There was a problem hiding this comment.
NAB: I think inline comments can be simplified and more user-friendly.
For now, they're difficult to read.
Examples:
…ct guarantee Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.33-0 🚀
|
|
🤖 I reviewed the changes and no help site updates are required under This PR is an internal sync-reliability change — it adds a stall guard to the client's
There is no change to any user-facing feature, setting, tab, or button, so nothing in the customer-facing help site needs to be updated. No draft docs PR was created. @adhorodyski, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
Explanation of Change
When the client detects a gap in server update IDs it fetches the missing range with
GetMissingOnyxMessages. Production traces show the server sometimes answers that fetch with a 200 that doesn't move the client forward. The client never advances, so every incoming Pusher update re-detects the same gap and fires the same fetch again, about once per second. The earlier fix in #93948 only coalesced fetches that overlap in time, so it cannot break this loop of back-to-back fetches.This PR adds a stall guard:
ReconnectApp, whose response always applies and moves the client to the server head, and raises aLog.alertso stalled clients are visible in monitoring.There is no timer machinery. The guard stores when it latched, and the check runs when the next Pusher update arrives anyway. Two ways out of the back-off:
What this guarantees:
Failed or unanswered fetches keep the existing retry behavior. Both fetch call sites in
OnyxUpdateManager(the gap branch and the pending-updates branch) share the guard, andcontributingGuides/SEQUENTIAL_QUEUE.mddocuments it.Supersedes #94974. The stall detection idea comes from @elirangoshen's work there; this PR reads the stall from the fetch response instead of a retry counter, and recovers the client instead of going silent.
Fixed Issues
$ #95486
PROPOSAL:
Tests
npm run test -- tests/unit/OnyxUpdateManagerTest.ts.GetMissingOnyxMessagesrequests in the network tab.Offline tests
GetMissingOnyxMessagesloop and no stuck or paused queue.QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
🤖 Generated with Claude Code