-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Cleanup Network.js code. Fix persisted request handling. #8306
Conversation
Fix tests. Move enhanceParameters to its own method add docs fix bad comment clean up some logic that does not quite make sense Add more logging Only treat "Failed to fetch" errors as retryable for now. Curious to see what other kinds of errors we get. Throw any fetch errors when the response is not ok Make tests pass. Stop retrying non 200 jsonCode. fix up tests Add a few more tests Fix tests
cc @roryabraham too if you can give this a look |
} | ||
|
||
onResponse(request, response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB: wouldn't be better to move onResponse
to processRequest
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm gonna look into this in my next clean up PR. Basically there are two different processRequest
happening here. One for regular requests run in the "main" queue and another for the persisted requests. Yes, I think there's probably a way to have just one handler for both. But I don't want to touch that yet.
// If we are catching a known network error like "Failed to fetch" allow this request to be retried if we have retries left | ||
if (error.message === CONST.ERROR.FAILED_TO_FETCH) { | ||
const retryCount = NetworkRequestQueue.incrementRetries(request); | ||
getLogger().info('Persisted request failed', false, {retryCount, command: request.command, error: error.message}); | ||
if (retryCount >= CONST.NETWORK.MAX_REQUEST_RETRIES) { | ||
getLogger().info('Request failed too many times removing from storage', false, {retryCount, command: request.command, error: error.message}); | ||
NetworkRequestQueue.removeRetryableRequest(request); | ||
} | ||
} else if (error.name === CONST.ERROR.REQUEST_CANCELLED) { | ||
getLogger().info('Persisted request was cancelled. Not retrying.'); | ||
onError(request); | ||
NetworkRequestQueue.removeRetryableRequest(request); | ||
} else { | ||
getLogger().alert(`${CONST.ERROR.ENSURE_BUGBOT} unknown error while retrying persisted request. Not retrying.`, { | ||
command: request.command, | ||
error: error.message, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB: I think we can also move this logic to processRequest
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same answer as before.
if (error.name === CONST.ERROR.REQUEST_CANCELLED) { | ||
onError(queuedRequest, error); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB: Also I wonder if we can / should handle this condition in processRequest
🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but next PR please. And you can give me some ideas on how to do it if you have them 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM I just left a few comments that I think can be useful for the next refactoring PRs
/** | ||
* @param {String} newAuthToken | ||
*/ | ||
function setAuthToken(newAuthToken) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why expose this function to set the authToken
directly, instead updating SESSION.authToken
in Onyx and relying on the Onyx.connect
callback to update the local authToken
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good eye. It's not quite a hack, but close. Setting values in Onyx always takes at least one tick of the event loop and updates subscribers late. If you look at where this is used it might make more sense.
Lines 236 to 237 in 9c6c2a9
authToken: parameters.authToken, | |
shouldRetry: false, |
Inside reauthenticate()
we are setting the "local" authToken since anything called after reauthenticate()
will need immediate access to the authToken
- otherwise it will use the old one again and get a 407
again.
I'll leave a comment here. Basically, Onyx being Onyx though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, makes sense.
Setting values in Onyx always takes at least one tick of the event loop and updates subscribers late ... Basically, Onyx being Onyx though
Is there a way we could fix this? Maybe we could create an issue in the Onyx repo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think we even tried once, but it broke things in unexpected ways and there is a high likelihood that it may break stuff again. Anyone using Onyx.merge()
is expecting to have those values available on the next tick (or later). Our current tests are pretty dependent on that "feature" as well so it's gonna be interesting if we ever try and do anything about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm seems to me like things might be a bit easier to reason about if one could safely assume than any value written in Onyx will be updated immediately, but I'll take your word for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They would 1000% be easier. And we could still do it. But whoever does it will need to check everything carefully and probably remove a lot of waitForPromisesToResolve()
calls from our tests 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created an issue: Expensify/react-native-onyx#123
It's not very detail-rich, but will hopefully explore more when I have some spare cycles.
*/ | ||
function isAuthTokenRequired(command) { | ||
return !_.contains([ | ||
'Log', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB alphabetize this list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je refuse!
src/libs/Network/index.js
Outdated
const retryCount = NetworkRequestQueue.incrementRetries(queuedRequest); | ||
getLogger().info('A retrieable request failed', false, { | ||
retryCount, | ||
// Retry and request that returns a "Failed to fetch" error. Very common if a user is offline or experiencing an unlikely scenario. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Retry and request that returns a "Failed to fetch" error. Very common if a user is offline or experiencing an unlikely scenario. | |
// Retry any request that returns a "Failed to fetch" error. Very common if a user is offline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes to the typo. Gonna leave the last part. There are other scenarios and they are unlikely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some of those scenarios.
const originalXHR = HttpUtils.xhr; | ||
|
||
beforeEach(() => { | ||
HttpUtils.xhr = originalXHR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB/nothing new, but isn't it weird that our HttpUtils
reference xhr
when really we use fetch
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like HttpUtils.request
might be a more fitting name 🤷
No blockers for me, just had some questions / minor suggestions. All of this is good cleanup, thanks for helping to tame this difficult code @marcaaron 🙇 |
Thanks for the reviews ! Updated and tried to add some better comments about the confusing things. |
✋ 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 @marcaaron in version: 1.1.47-0 🚀
|
🚀 Deployed to production by @roryabraham in version: 1.1.49-1 🚀
|
cc @marcochavezf
Details
This PR does some cleanup of the Network code to help get things ready for Sync Write Queue changes that we will be adding soon. I will be following up with more PRs to modularize and organize the code, but for this PR the changes are:
HTTP_STATUS_CODE
const withJSON_CODE
(since astatus
code and ajsonCode
are NOT the same and this could easily confuse people)jsonCode
can just have the response handled byonResponse
.catch()
. I checked server logs and we have mostly thrownFailed to fetch
and ajsonCode 407
. I updated this logic so that only "Failed to fetch" errors will get "retried"ENSURE_BUGBOT
log line so we can see what these errors actually are and then decide how to handle them.fetch()
is not tracking bad responses or doing anything about them. I added a throw toHttpUtils.xhr()
(which will trigger theENSURE_BUGBOT
so we can see what these things are and handle them).enhanceParameters
and moved it to a separate file instead of using the "callback" trick.Onyx.connect()
into a "store" file so that we can check for the "network ready" and update the localauthToken
fromAPI.js
.Fixed Issues
No issue, but related to the Offline First doc preparation
Tests
Automated tests were added
Do QA steps
Verify that no errors appear in the JS console
QA Steps
Test behavior of offline queue