Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network: Persisted Requests Queue - clear persisted requests after the response #6556

Merged
merged 13 commits into from
Jan 31, 2022

Conversation

kidroca
Copy link
Contributor

@kidroca kidroca commented Dec 1, 2021

Details

Additional functionality was added to Network.js and NetworkRequestQueue.js to better handle persisted requests

  • clear requests from persistent storage only after they are completed
  • retry persisted request

Added unit tests covering persisted request handling

Fixed Issues

$ #5987

Tests

  1. Launch the app
  2. Go offline
  3. Write a few messages
  4. Close the app
  5. Be online
  6. Open the app
  7. Observe the messages sent while being offline are correctly sent to the backend

QA Steps

Follow the steps from the bug report

  1. Make sure you are offline.
  2. Send some messages to any user.
  3. Refresh the page on the web. Close & open the app on mobile.
  4. Now you can still see the messages.
  5. Now enable internet
  6. Shortly (a second after resuming the connection) after refresh or close the app
  7. The messages should be posted after the refresh or after you open the app again
  • note: the order of the messages might be incorrect, this PR is not addressing the order

This comment has additional details on steps and timing: #5987 (comment)

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

Web

New.Expensify.-.Google.Chrome.2021-12-01.12-51-05.mp4

Mobile Web

Android.Emulator.-.Pixel_2_API_29_5554.2021-12-01.13-11-03.mp4

Desktop

Screen.Recording.2021-12-01.at.13.22.10.mov

iOS

Screen.Recording.2021-12-01.at.14.45.00.mov

Android

Android.Emulator.-.Pixel_2_API_29_5554.2021-12-01.08-49-22.mp4

Copy link
Contributor Author

@kidroca kidroca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some highlights on the changes

Comment on lines +88 to +89
// Unless email is already set include current user's email in every request and the server logs
finalParameters.email = lodashGet(parameters, 'email', currentUserEmail);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was originally part of the network "forEach" here:

Network.js

        // If we haven't passed an email in the request data, set it to the current user's email
        if (email && _.isEmpty(requestEmail)) {
            requestData.email = email;
        }

        const finalParameters = _.isFunction(enhanceParameters)
            ? enhanceParameters(queuedRequest.command, requestData)
            : requestData;

Here (addDefaultParameters) seems to be a better place for this logic

Comment on lines +64 to +67
// Do a recursive call in case the queue is not empty after processing the current batch
return Promise.all(tasks)
.then(processPersistedRequestsQueue);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have remaining requests when

  • the request failed for some reason and is not removed from the queue - as seen in the catch block
  • we became offline again, which would again lead to the catch block above. In that case the queue would still stop and will be retriggered once we're back online, more requests can be added to it in the meantime

Not sure if we should capture that in a comment or something

@@ -328,7 +316,6 @@ function clearRequestQueue() {
export {
post,
pauseRequestQueue,
PROCESS_REQUEST_DELAY_MS,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now exported from CONST.NETWORK.PROCESS_REQUEST_DELAY_MS and usages updated

Comment on lines +30 to +36
function incrementRetries(request) {
const current = retryMap.get(request) || 0;
const next = current + 1;
retryMap.set(request, next);

return next;
}
Copy link
Contributor Author

@kidroca kidroca Dec 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retry counts are not persisted to disk - no need to
If a request is retried 5 times then the app quit, when the app is launched again the request will have a fresh count of up to 10 attempts, and not only the remaining 5

Might be an edge case but I think it would be best to have a retry limit in case some corrupt data was persisted and it cannot be sent no matter how many times we try.
That's why we keeping a count per request here and we forfeit a request after the count grows past a certain limit (CONST.NETWORK.MAX_PERSISTED_REQUEST_RETRIES)

@kidroca kidroca marked this pull request as ready for review December 1, 2021 12:48
@kidroca kidroca requested a review from a team as a code owner December 1, 2021 12:48
@MelvinBot MelvinBot removed the request for review from a team December 1, 2021 12:48
@botify botify requested review from johnmlee101 and a team December 1, 2021 12:48
@MelvinBot MelvinBot removed the request for review from a team December 1, 2021 12:48
@johnmlee101
Copy link
Contributor

@roryabraham do you mind co-reviewing this with me?

src/CONST.js Outdated Show resolved Hide resolved
let isQueuePaused = false;
let persistedRequestsQueueRunning = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that this value is sort of the inverse of isQueuePaused. I think we should try to keep these two things consistent, so maybe one of the following two options:

let isQueuePaused = false;
let isOfflineQueuePaused = true;

or

let isMainQueueRunning = true;
let isOfflineQueueRunning = false;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name comes from this usagage and readability reasons
This method is starting something rather than making it not paused

function flushPersistedRequestsQueue() {
    if (persistedRequestsQueueRunning) {
        return;
    }

    persistedRequestsQueueRunning = true;

vs

function flushPersistedRequestsQueue() {
    if (!persistedRequestsQueuePaused) {
        return;
    }

   persistedRequestsQueuePaused = false;

running is the "special case" for this queue, while paused is the special case for the regular queue

I'm reluctant to refactor isQueuePaused to isMainQueueRunning it would change usages and might affect their intent as well, also it kind a sound like we're online too

src/libs/Network.js Show resolved Hide resolved
src/libs/Network.js Outdated Show resolved Hide resolved
@roryabraham
Copy link
Contributor

I had only surface-level comments on this implementation, but will bring higher-level discussion to the linked issue.

@kidroca kidroca requested a review from roryabraham December 14, 2021 23:52
@kidroca
Copy link
Contributor Author

kidroca commented Dec 17, 2021

This is ready for review
I've made some small changes to address Rory's concerns.

@mallenexpensify
Copy link
Contributor

@johnmlee101 @roryabraham can you review this PR? @kidroca 's been waiting three weeks

@kidroca
Copy link
Contributor Author

kidroca commented Jan 14, 2022

Sorry, this accidentally got closed: #4908 (comment)

@thienlnam thienlnam reopened this Jan 14, 2022
@kidroca
Copy link
Contributor Author

kidroca commented Jan 26, 2022

@johnmlee101 @roryabraham
Can you post an update
What do I need to do to move this forward?

@kidroca
Copy link
Contributor Author

kidroca commented Jan 26, 2022

Oh, I see there are conflicts
I'll be able to merge and test again tomorrow

@kidroca
Copy link
Contributor Author

kidroca commented Jan 28, 2022

@johnmlee101 @roryabraham @mallenexpensify
Before I merge main, fix the conflicts and retest everything can someone at least confirm that's the only thing left to do and there are no requested changed

@johnmlee101
Copy link
Contributor

I think the changes right now are good, and I'll be ready to approve once the conflicts are resolved.

@roryabraham
Copy link
Contributor

Yeah, I looked this over again and I don't think I have any further comments.

@kidroca
Copy link
Contributor Author

kidroca commented Jan 31, 2022

I'll submit a merge and fix conflicts shortly

# Conflicts:
#	tests/unit/NetworkTest.js
@kidroca
Copy link
Contributor Author

kidroca commented Jan 31, 2022

Synced with main and retested - everything working as intended

@roryabraham roryabraham merged commit c2e34c1 into Expensify:main Jan 31, 2022
@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

OSBotify commented Feb 1, 2022

🚀 Deployed to staging by @roryabraham in version: 1.1.33-4 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

OSBotify commented Feb 2, 2022

🚀 Deployed to production by @sketchydroide in version: 1.1.34-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

}

function saveRetryableRequests(retryableRequests) {
Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests);
persistedRequests = lodashUnionWith(persistedRequests, retryableRequests, _.isEqual);
Copy link
Contributor

@sobitneupane sobitneupane Feb 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change has introduced this bug. (Title: pinned chat become unpinned when user pinned chat in offline mode)

If a user takes an action that is identical(_.isEqual) to older action then it will be removed.

To fix the issue we have replace above code by:

persistedRequests = persistedRequests.concat(requestsToPersist);

The PR that fixes the issue: #14608

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to prevent the user from accumulate duplicate requests while offline
If you perform the same action 10x while offline, we skip posting 10 Network request but just one
This is an optimization that helps users in low network conditions to not waste bandwidth

From the information shared here it seems that pinning and unpinning the chat uses the same command / request, I would suggest to make them distinct actions so that _.isEqual can differentiate them correctly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants