Skip to content

Retry requests if storage (auth + credentials) is not ready#6183

Merged
marcaaron merged 30 commits into
mainfrom
marco-checkIfOnyxIsReady3
Nov 8, 2021
Merged

Retry requests if storage (auth + credentials) is not ready#6183
marcaaron merged 30 commits into
mainfrom
marco-checkIfOnyxIsReady3

Conversation

@marcochavezf

@marcochavezf marcochavezf commented Nov 2, 2021

Copy link
Copy Markdown
Contributor

cc @marcaaron @tgolen

Details

There's an issue on Android where the user is logged out randomly. We're not 100% sure about the root cause, but one possibility is the existence of a race condition between the async storage and the networks requests; where the auth token and credentials are not read yet from persistent storage (Onyx) while a network request is made.

I was able to reproduce this scenario by adding a setTimeout in the Onyx.connect callback (more details in this comment):

Screen.Recording.2021-10-12.at.18.32.38.mov

There's a long discussion in the issue, where different approaches are discussed (both big and small changes). But we agreed on tackling the issue atm without changing the architecture of the Network Queue. This solution is comprised of the following points:

  • Add a unit test (StoreNetworkTest.js) that simulates the scenario where auth token + credentials are not ready.
  • Add the flag isReady and its setter to Network.js to process requests only if auth token + credentials are already read from storage, otherwise re-queue the requests to retry later (as long as the request don't have the property doNotRetry = true). Also, log a message to the server when this scenario happens.
  • Stop logging out in enhanceParameters (which is addDefaultValuesToParameters in API.js), since we're already handling that auth token + credentials are read first and the http request should logout the user if the auth token is inexistent or expired (server should return a 407 jsonCode if we are online). Also, the API.js has changed a lot since the logout (redirectToSignIn()) was added and probably is no longer needed because that's already handled in handleExpiredAuthToken.

Also a few extra comments:

  • In order to use the Log in Network I disabled the eslint circular dependency warning (hopefully we can fix this later with a better dependency or another design).
  • Fixed a few minor jsdocs types.
  • Added explicit Jest globals to get rid of errors in vscode and have better IntelliSense.
  • I updated some unit tests to verify the retry is not happening (which is an ideal scenario where auth token + credentials are already read when a network request happens).
  • Also added Onyx.init and Onyx.clear in some tests, because for some reason the Onyx.connect callback was not triggered and those tests were failing.

Fixed Issues

$ #5619

Tests / QA Steps

In Chrome devtools go to Application -> Local Storage and:

  • Mess up the authToken (session) and make sure you are reauthenticated.
  • Mess up the credentials and authToken and make sure you are logged out.

Screen Shot 2021-11-08 at 11 22 45

N/A

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

N/A

@marcochavezf marcochavezf requested a review from a team as a code owner November 2, 2021 23:48
@MelvinBot MelvinBot requested review from nickmurray47 and removed request for a team November 2, 2021 23:48
@marcochavezf marcochavezf changed the title Retry requests if storage (auth + credentials) is not ready [HOLD] Retry requests if storage (auth + credentials) is not ready Nov 2, 2021
@marcochavezf marcochavezf changed the title [HOLD] Retry requests if storage (auth + credentials) is not ready Retry requests if storage (auth + credentials) is not ready Nov 3, 2021
@marcochavezf marcochavezf self-assigned this Nov 3, 2021
@marcaaron marcaaron self-requested a review November 3, 2021 01:18
Comment thread src/libs/API.js Outdated
Comment thread src/libs/API.js
Comment thread tests/unit/StoreNetworkTest.js Outdated
Comment thread tests/unit/StoreNetworkTest.js Outdated
Comment thread tests/unit/StoreNetworkTest.js Outdated
Comment thread src/libs/Network.js Outdated
Comment thread tests/actions/ReportTest.js Outdated
Comment thread tests/actions/SessionTest.js Outdated
Comment thread src/libs/Network.js
Comment thread tests/unit/StoreNetworkTest.js Outdated

@nickmurray47 nickmurray47 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The changes lgtm afaict - looks like there's one linting issue and also wondering if we can add manual tests for regressions to make sure we aren't breaking anything.

@marcochavezf

Copy link
Copy Markdown
Contributor Author

Since we are setting the Network to "ready" when the session and credentials are available should we set it back to "not ready" when we sign out? If the Network was still in the "ready" state and we tried to sign in I'm curious if we would run into any issues if e.g. their local variables were not yet set? 🤔

Hmm I don't think so, because the Network.setIsReady(true) should be only called when the app is booting up to avoid the race condition between storage and network. Once session and credentials (existent or non-existent) are read from storage the Network should be ready until the app is closed by the OS. I think we should only set the network to "not ready" if the storage fails for some reason while the app is still running on a handset (not sure if that's something the OS handles making the app crash, but in any case should be a rare scenario).

@marcaaron

Copy link
Copy Markdown
Contributor

Hmm I don't think so, because the Network.setIsReady(true) should be only called when the app is booting up to avoid the race condition between storage and network.

Yeah I was thinking that when you sign out the storage is cleared so the values for session and credentials look like this:

2021-11-04_12-41-48

But since we "cache-first" there's no way for an Onyx.set() or Onyx.merge() to cause a "slow" Onyx.connect() on sign in.

https://github.com/Expensify/react-native-onyx/blob/224e2ba03f2365726b8c1d0785e750c54a85332b/lib/Onyx.js#L530-L537

Comment thread tests/unit/NetworkTest.js Outdated
change unit test comments to GIVE-WHEN-THEN format
@marcochavezf

Copy link
Copy Markdown
Contributor Author

Hmm I don't think so, because the Network.setIsReady(true) should be only called when the app is booting up to avoid the race condition between storage and network.

Yeah I was thinking that when you sign out the storage is cleared so the values for session and credentials look like this:

2021-11-04_12-41-48

But since we "cache-first" there's no way for an Onyx.set() or Onyx.merge() to cause a "slow" Onyx.connect() on sign in.

https://github.com/Expensify/react-native-onyx/blob/224e2ba03f2365726b8c1d0785e750c54a85332b/lib/Onyx.js#L530-L537

Ah yeah but even if we don't read the values from the cache after signing out, I don't think we should have a problem; the storage and the network are ready and the network loop should handle any bad state.

marcaaron
marcaaron previously approved these changes Nov 4, 2021

@marcaaron marcaaron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

It would be nice to see if the test can be improved further, but maybe not crucial to do right now. I think what we've come up works OK for now.

Comment thread src/libs/OptionsListUtils.js Outdated
* @param {Object} personalDetails
* @param {String} searchValue
* @param {Array<String>} betas
* @param {Array<String>=} betas

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Forgot this one

@marcaaron

Copy link
Copy Markdown
Contributor

Maybe merging main will fix the e2e test or we can restart it.

@marcochavezf

Copy link
Copy Markdown
Contributor Author

It would be nice to see if the test can be improved further, but maybe not crucial to do right now. I think what we've come up works OK for now.

Yeah, hopefully, we can improve later this test (which I think we'll require a few changes in API.js to have better control of the Onyx.connect callbacks). Also in the coverage report (running npm run test -- --coverage) I noticed we cover 75.58% of Network.js code in the unit tests:

Screen Shot 2021-11-05 at 12 40 07

And given the importance of Network.js in the app, should we add more unit tests to cover if possible 100% in this file? 🤔

Maybe merging main will fix the e2e test or we can restart it.

I added another commit with more comments in the unit test and now the e2e test passed 😄

marcaaron
marcaaron previously approved these changes Nov 5, 2021
@marcaaron

Copy link
Copy Markdown
Contributor

Left a follow up comment about the JS doc, but I think this is good to merge.

@nickmurray47

Copy link
Copy Markdown
Contributor

Not sure if tests failing had anything to do with the recent change but restarted them to see if they'll pass again

nickmurray47
nickmurray47 previously approved these changes Nov 7, 2021

@nickmurray47 nickmurray47 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tests pass now! Feel free to merge and @marcochavezf do you mind adding these QA steps to the PR from this comment:

  • Mess up the authToken and make sure you are reauthenticated
  • Mess up the credentials and authToken and make sure you are logged out

@marcaaron marcaaron merged commit ccdf729 into main Nov 8, 2021
@marcaaron marcaaron deleted the marco-checkIfOnyxIsReady3 branch November 8, 2021 16:52
@OSBotify

OSBotify commented Nov 8, 2021

Copy link
Copy Markdown
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
Copy Markdown
Contributor

🚀 Deployed to staging by @marcaaron in version: 1.1.14-5 🚀

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

@isagoico

Copy link
Copy Markdown

@marcaaron @marcochavezf Hello! I'm not sure if were testing this correctly. What I did was:

  1. Navigate to Application in dev tools
  2. In local storage search for session
  3. In the session info I changed the Authtoken and the email. I just added a bunch of random characters to mess it up

After that, I was not logged out and it looked like I was logged in as the user I randomly wrote on the email
image

@marcochavezf

Copy link
Copy Markdown
Contributor Author

@marcaaron @marcochavezf Hello! I'm not sure if were testing this correctly. What I did was:

  1. Navigate to Application in dev tools
  2. In local storage search for session
  3. In the session info I changed the Authtoken and the email. I just added a bunch of random characters to mess it up

After that, I was not logged out and it looked like I was logged in as the user I randomly wrote on the email image

Ah from the app side I think this is fine because we're only verifying that authToken (from session object) and the credentials object (which is another object in the local storage) are valid or existent to keep the user logged in (which are retrieved from the server when the user enters their correct email and password). And the email from the session object is only used for the UI and not used to re-login the user.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by @AndrewGable in version: 1.1.15-15 🚀

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

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.

5 participants