-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8312 from Expensify/marcaaron-networkCleanup2
Network Cleanup: Isolate "persisted " queue from "main" queue
- Loading branch information
Showing
14 changed files
with
407 additions
and
342 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import CONST from '../../CONST'; | ||
import createCallback from '../createCallback'; | ||
|
||
const [getLogger, registerLogHandler] = createCallback(); | ||
const [triggerConnectivityResumed, onConnectivityResumed] = createCallback(); | ||
const [triggerRequestMade, onRequestMade] = createCallback(); | ||
const [triggerResponse, onResponse] = createCallback(); | ||
const [triggerError, onError] = createCallback(); | ||
const [triggerRecheckNeeded, onRecheckNeeded] = createCallback(); | ||
|
||
/** | ||
* @returns {Function} cancel timer | ||
*/ | ||
function startRecheckTimeoutTimer() { | ||
// If request is still in processing after this time, we might be offline | ||
const timerID = setTimeout(triggerRecheckNeeded, CONST.NETWORK.MAX_PENDING_TIME_MS); | ||
return () => clearTimeout(timerID); | ||
} | ||
|
||
export { | ||
registerLogHandler, | ||
getLogger, | ||
triggerConnectivityResumed, | ||
onConnectivityResumed, | ||
triggerRequestMade, | ||
onRequestMade, | ||
onResponse, | ||
triggerResponse, | ||
onError, | ||
triggerError, | ||
triggerRecheckNeeded, | ||
onRecheckNeeded, | ||
startRecheckTimeoutTimer, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import _ from 'underscore'; | ||
import Onyx from 'react-native-onyx'; | ||
import * as PersistedRequests from '../actions/PersistedRequests'; | ||
import * as NetworkStore from './NetworkStore'; | ||
import * as NetworkEvents from './NetworkEvents'; | ||
import CONST from '../../CONST'; | ||
import ONYXKEYS from '../../ONYXKEYS'; | ||
import * as ActiveClientManager from '../ActiveClientManager'; | ||
import processRequest from './processRequest'; | ||
|
||
let isPersistedRequestsQueueRunning = false; | ||
|
||
/** | ||
* This method will get any persisted requests and fire them off in parallel to retry them. | ||
* If we get any jsonCode besides 407 the request is a success. It doesn't make sense to | ||
* continually retry things that have returned a response. However, we can retry any requests | ||
* with known networking errors like "Failed to fetch". | ||
* | ||
* @returns {Promise} | ||
*/ | ||
function process() { | ||
const persistedRequests = PersistedRequests.getAll(); | ||
|
||
// This sanity check is also a recursion exit point | ||
if (NetworkStore.getIsOffline() || _.isEmpty(persistedRequests)) { | ||
return Promise.resolve(); | ||
} | ||
|
||
const tasks = _.map(persistedRequests, request => processRequest(request) | ||
.then((response) => { | ||
if (response.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { | ||
NetworkEvents.getLogger().info('Persisted optimistic request needs authentication'); | ||
} else { | ||
NetworkEvents.getLogger().info('Persisted optimistic request returned a valid jsonCode. Not retrying.'); | ||
} | ||
NetworkEvents.triggerResponse(request, response); | ||
PersistedRequests.remove(request); | ||
}) | ||
.catch((error) => { | ||
// Make this request if we are catching a known network error like "Failed to fetch" and have retries left | ||
if (error.message === CONST.ERROR.FAILED_TO_FETCH) { | ||
const retryCount = PersistedRequests.incrementRetries(request); | ||
NetworkEvents.getLogger().info('Persisted request failed', false, {retryCount, command: request.command, error: error.message}); | ||
if (retryCount >= CONST.NETWORK.MAX_REQUEST_RETRIES) { | ||
NetworkEvents.getLogger().info('Request failed too many times, removing from storage', false, {retryCount, command: request.command, error: error.message}); | ||
PersistedRequests.remove(request); | ||
} | ||
} else if (error.name === CONST.ERROR.REQUEST_CANCELLED) { | ||
NetworkEvents.getLogger().info('Persisted request was cancelled. Not retrying.'); | ||
NetworkEvents.triggerError(request); | ||
PersistedRequests.remove(request); | ||
} else { | ||
NetworkEvents.getLogger().alert(`${CONST.ERROR.ENSURE_BUGBOT} unknown error while retrying persisted request. Not retrying.`, { | ||
command: request.command, | ||
error: error.message, | ||
}); | ||
PersistedRequests.remove(request); | ||
} | ||
})); | ||
|
||
// Do a recursive call in case the queue is not empty after processing the current batch | ||
return Promise.all(tasks) | ||
.then(process); | ||
} | ||
|
||
function flush() { | ||
if (isPersistedRequestsQueueRunning) { | ||
return; | ||
} | ||
|
||
// ONYXKEYS.PERSISTED_REQUESTS is shared across clients, thus every client/tab will have a copy | ||
// It is very important to only process the queue from leader client otherwise requests will be duplicated. | ||
if (!ActiveClientManager.isClientTheLeader()) { | ||
return; | ||
} | ||
|
||
isPersistedRequestsQueueRunning = true; | ||
|
||
// Ensure persistedRequests are read from storage before proceeding with the queue | ||
const connectionID = Onyx.connect({ | ||
key: ONYXKEYS.PERSISTED_REQUESTS, | ||
callback: () => { | ||
Onyx.disconnect(connectionID); | ||
process() | ||
.finally(() => isPersistedRequestsQueueRunning = false); | ||
}, | ||
}); | ||
} | ||
|
||
// Flush the queue when the connection resumes | ||
NetworkEvents.onConnectivityResumed(flush); | ||
|
||
export { | ||
// eslint-disable-next-line import/prefer-default-export | ||
flush, | ||
}; |
Oops, something went wrong.