fix: back off GetMissingOnyxMessages once it stops advancing the client#94974
fix: back off GetMissingOnyxMessages once it stops advancing the client#94974elirangoshen wants to merge 2 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@mkhutornyi 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] |
|
that CLA seems error flaky check, can you run it again ? |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safari |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cace02e0e6
ℹ️ 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".
| setMissingOnyxUpdatesQueryPromise(Promise.resolve()); | ||
| return true; |
There was a problem hiding this comment.
Preserve the deferred update when backing off
When this backoff branch runs after enqueueing the gap update, returning true attaches finalizeUpdatesAndResumeQueue, which calls clearDeferredOnyxUpdates() and clears the just-deferred server update without fetching or applying it. In the scenario where the guard trips for update 3 while the client is at 1, and update 2 later arrives contiguously and advances the client, update 3 has already been discarded, so the client remains stale until an unrelated later update or reconnect happens.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is intentional — the deferred update is dropped here on purpose, and it's not permanently lost. The guard only trips after the client has been stuck for 3 consecutive stalled fetches, i.e. the gap isn't closing. Once the client advances again (via reconnect or the next gap push), the counter resets and the following GetMissingOnyxMessages re-fetches the whole range, including this update — so it self-heals.
Preserving it here would actually be worse: a kept deferred update hits the areDeferredUpdatesQueued → return false gate, and contiguous updates don't drain the deferred queue, so it could stall until a reconnect instead of self-healing. The bounded staleness is the intended trade-off for breaking the request loop.
|
PR doesn’t need product input as a performance PR. Unassigning and unsubscribing myself. |
# Conflicts: # tests/unit/OnyxUpdateManagerTest.ts
|
Please fix lint |
|
I think this also needs updates to the |
|
hm after the guard trips, the user stops getting data until they reload (if you're on an active tab, refocus guards to not work and sync you back). Why not fire one full |
|
@MelvinBot review |
ReviewVerdict: The approach is sound and well-scoped — it correctly closes the sequential-fetch gap that the concurrency guard in #93948 left open. Logic traces cleanly, the self-heal is real, and the backoff resumes the queue. No blocking bugs found. A couple of things worth confirming before merge. Why the logic holds up
Two things to confirm:
Nit: The new regression test asserts GMOM call counts but not that the queue actually unpauses on backoff ( I couldn't run |
|
Honestly I don't believe that Melvin is right here, this does not feel right to me as we're working around the problem and not on it. Backing off silently leaves users stale: after the guard trips, the only recovery is a Here's what I'd do for this as per my comment above:
|
|
Spoke to Eliran, I will take over this PR. |
|
fyi I will recreate this PR so I can own it end to end with the description. |
|
Continuing this here #95477 |
Explanation of Change
When the app detects a gap in server update IDs it fetches the missing ones via
GetMissingOnyxMessages(GMOM). A prior fix (#93948) coalesced concurrent GMOM requests — the "is one already running right now" guard inOnyxUpdateManager's gap branch.That guard only catches fetches that overlap in time. Production telemetry shows the loop survives across sequential fetches: a request finishes,
finalizeUpdatesAndResumeQueueclears the in-flight marker, the server's next push still reports a gap, and because nothing is running at that instant the guard lets a new GMOM through. When the gap never closes —lastUpdateIDAppliedToClientdoes not advance — the app fires GMOM ~once a second while the screen sits frozen (a real trace fired 25 requests in ~8s under a single 14.8s route change).This adds a convergence guard to the same gap branch:
consecutiveStalledGapFetches). When a fetch advances the client,lastUpdateIDFromClientdiffers on the next entry and the counter resets.CONST.MAX_CONSECUTIVE_STALLED_GAP_FETCHES(3), it resumes the SequentialQueue without firing another GMOM and logs an alert. The counter stays tripped until the client advances via any other path (aReconnectAppon app foreground / network reconnect, or a contiguous Pusher update), which self-heals — so no queue freeze and no dropped catch-up.The change is scoped to the gap branch decision; the demand/pending/reconnect branches are untouched.
Fixed Issues
$ #92541
PROPOSAL:
Tests
npm run test -- tests/unit/OnyxUpdateManagerTest.ts.should stop re-fetching missing updates once GetMissingOnyxMessages stops advancing the client, which:MAX_CONSECUTIVE_STALLED_GAP_FETCHEScycles,Offline tests
QA Steps
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
Screen.Recording.2026-07-01.at.14.02.47.mov