Repository navigation
Treat an in-flight reconnect as covering the gap it is already fetching - #100320
Conversation
reconnectApp sends its request through the sequential queue but never arms setMissingOnyxUpdatesQueryPromise, so nothing on the client records that a catch-up range is outstanding. The stored update ID therefore sits still for the whole request flight (p50 0.80s, p90 3.43s), and a Pusher event arriving in that window compares against the stale position and reads as a hole. The client then fires GetMissingOnyxMessages for a range the reconnect is already carrying: 97.8% of the fetches produced this way asked for a range wholly inside the in-flight reconnect, median 355,329 update IDs. Read the queue's own in-flight record instead of adding a second marker. When the request on the wire is an OpenApp or a ReconnectApp whose coverage starts at or below the client's position, wait on getCurrentRequest() and then re-validate the deferred updates, which re-reads the position and fetches only what is still missing. Only the request on the wire qualifies. A reconnect still queued behind other WRITEs never sends while the queue is paused for this gap, so waiting on it would hold the pause open until the watchdog. The shouldFetchPendingUpdates branch is deliberately left alone: it never enqueues a deferred update, so suppressing its fetch would drop the range up to pendingLastUpdateID. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/libs/actions/OnyxUpdateManager/index.ts # tests/unit/OnyxUpdateManagerTest.ts
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|
@MelvinBot please review this PR |
|
🤖 Code review — the approach is sound and the tests are unusually good at attacking it. One issue is worth fixing before merge, plus two smaller robustness notes. The coverage predicate matches the convention already used by 1.
|
…red updates Only the Airship push path passes a clientLastUpdateID, and it snapshots the watermark before the catch-up it is about to wait on has landed. Pinning to it let superseded updates past the minUpdateID filter, and OnyxUpdates.apply re-applies successData/failureData/finallyData for HTTPS updates even when the update is old, so an old payload could write over newer data. It also set the refetch range from the stale position. The pin still has to win when it is ahead of the live value: in Headless JS no Onyx callbacks run, so getEffectiveLastUpdateID reads 0 and simply dropping the argument refetches from 0. Math.max keeps both cases correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
On the extra assert Melvin proposed, we should do nothing. No need to guard against a potential future refactor breaking it. |
|
@ZhenjaHorbach 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] |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp2026-09-08.13.14.59.movAndroid: mWeb Chrome2026-09-08.13.14.59.moviOS: HybridApp2026-09-08.13.11.45.moviOS: mWeb Safari2026-09-08.13.10.00.movMacOS: Chrome / Safari2026-09-08.13.00.51.mov |
|
LGTM! |
|
🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.74-0 🚀
|
|
🤖 Help site review: no changes required to This PR changes only client-side update reconciliation. Nothing it touches is documented on the help site, so I did not create a draft PR. WhyWhat changed — all four files are internal sync/network plumbing with no user-facing surface:
Nothing documented is affected. The help site documents features, settings, and workflows people interact with. This PR adds no feature, renames no tab or setting, adds no button, and changes no copy. Your own QA steps confirm it: "No user-visible behaviour changes on its own." The observable effect — fewer stalls and redundant requests after coming back online — is a performance improvement, and the help site does not document App sync internals, reconnect behavior, or offline update reconciliation. Searched for candidate articles. I checked No live UI verification was needed. The naming and capitalization checks in Since no docs changes are required, there is no linked help site PR to review or mark |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.4.74-2 🚀
|
Explanation of Change
A record that looks missing while the app's own reconnect is still fetching it counts as already on its way, so the app waits for that reconnect's answer and then asks only for whatever it did not bring back.
Scope: this covers the 0.8s–3.4s a catch-up request spends on the wire, which #99774's marker does not reach: that one records an update already being written locally, after a response lands. The two shares are not additive.
Fixed Issues
$ #100471
PROPOSAL: N/A
Tests
npm run test -- tests/unit/OnyxUpdateManagerTest.ts. All 33 pass.[OnyxUpdateManager]and the Network tab filtered toapi.ReconnectApprequest fires, and that while it is still pending the console logsGap detected in update IDs from the server, but an in-flight reconnect already covers it, so waiting for its response.GetMissingOnyxMessagesrequest fires next to it in the Network tab.Offline tests
GetMissingOnyxMessages, which is the path this change deliberately leaves intact.CONST.NETWORK.MAX_RETRY_WAIT_TIME_MS) rather than skipped, and the pause watchdog is the backstop.QA Steps
No user-visible behaviour changes on its own. It removes redundant
GetMissingOnyxMessagesfetches after a reconnect and the write-queue pauses that accompany them, so the observable effect is fewer stalls when the app comes back online.PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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
No UI changes — this PR only removes a redundant network request and the write-queue pause that came with it.
Android: Native
N/A — no UI changes.
Android: mWeb Chrome
N/A — no UI changes.
iOS: Native
N/A — no UI changes.
iOS: mWeb Safari
N/A — no UI changes.
MacOS: Chrome / Safari
N/A — no UI changes.