Skip to content

Commit 791058a

Browse files
authored
Merge pull request #42044 from margelo/@chrispader/add-deferred-updates--queue-functions
Add deferred updates queue functions to `OnyxUpdateManager` to manually apply updates (e.g. from push notifications)
2 parents cb5c597 + 342873e commit 791058a

File tree

4 files changed

+162
-40
lines changed

4 files changed

+162
-40
lines changed

src/libs/actions/OnyxUpdateManager/index.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import Log from '@libs/Log';
55
import * as SequentialQueue from '@libs/Network/SequentialQueue';
66
import * as App from '@userActions/App';
77
import ONYXKEYS from '@src/ONYXKEYS';
8-
import type {OnyxUpdatesFromServer, Response} from '@src/types/onyx';
8+
import type {OnyxUpdatesFromServer} from '@src/types/onyx';
99
import {isValidOnyxUpdateFromServer} from '@src/types/onyx/OnyxUpdatesFromServer';
1010
import * as OnyxUpdateManagerUtils from './utils';
11-
import deferredUpdatesProxy from './utils/deferredUpdates';
11+
import * as DeferredOnyxUpdates from './utils/DeferredOnyxUpdates';
1212

1313
// This file is in charge of looking at the updateIDs coming from the server and comparing them to the last updateID that the client has.
1414
// If the client is behind the server, then we need to
@@ -39,8 +39,6 @@ Onyx.connect({
3939
},
4040
});
4141

42-
let queryPromise: Promise<Response | Response[] | void> | undefined;
43-
4442
let resolveQueryPromiseWrapper: () => void;
4543
const createQueryPromiseWrapper = () =>
4644
new Promise<void>((resolve) => {
@@ -50,8 +48,7 @@ const createQueryPromiseWrapper = () =>
5048
let queryPromiseWrapper = createQueryPromiseWrapper();
5149

5250
const resetDeferralLogicVariables = () => {
53-
queryPromise = undefined;
54-
deferredUpdatesProxy.deferredUpdates = {};
51+
DeferredOnyxUpdates.clear({shouldUnpauseSequentialQueue: false});
5552
};
5653

5754
// This function will reset the query variables, unpause the SequentialQueue and log an info to the user.
@@ -61,9 +58,7 @@ function finalizeUpdatesAndResumeQueue() {
6158
resolveQueryPromiseWrapper();
6259
queryPromiseWrapper = createQueryPromiseWrapper();
6360

64-
resetDeferralLogicVariables();
65-
Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null);
66-
SequentialQueue.unpause();
61+
DeferredOnyxUpdates.clear();
6762
}
6863

6964
/**
@@ -111,25 +106,24 @@ function handleOnyxUpdateGap(onyxUpdatesFromServer: OnyxEntry<OnyxUpdatesFromSer
111106
// The flow below is setting the promise to a reconnect app to address flow (1) explained above.
112107
if (!lastUpdateIDFromClient) {
113108
// If there is a ReconnectApp query in progress, we should not start another one.
114-
if (queryPromise) {
109+
if (DeferredOnyxUpdates.getMissingOnyxUpdatesQueryPromise()) {
115110
return;
116111
}
117112

118113
Log.info('Client has not gotten reliable updates before so reconnecting the app to start the process');
119114

120115
// Since this is a full reconnectApp, we'll not apply the updates we received - those will come in the reconnect app request.
121-
queryPromise = App.finalReconnectAppAfterActivatingReliableUpdates();
116+
DeferredOnyxUpdates.setMissingOnyxUpdatesQueryPromise(App.finalReconnectAppAfterActivatingReliableUpdates());
122117
} else {
123118
// The flow below is setting the promise to a getMissingOnyxUpdates to address flow (2) explained above.
124119

125-
// Get the number of deferred updates before adding the new one
126-
const existingDeferredUpdatesCount = Object.keys(deferredUpdatesProxy.deferredUpdates).length;
120+
const areDeferredUpdatesQueued = !DeferredOnyxUpdates.isEmpty();
127121

128122
// Add the new update to the deferred updates
129-
deferredUpdatesProxy.deferredUpdates[Number(updateParams.lastUpdateID)] = updateParams;
123+
DeferredOnyxUpdates.enqueue(updateParams, {shouldPauseSequentialQueue: false});
130124

131125
// If there are deferred updates already, we don't need to fetch the missing updates again.
132-
if (existingDeferredUpdatesCount > 0) {
126+
if (areDeferredUpdatesQueued) {
133127
return;
134128
}
135129

@@ -142,10 +136,12 @@ function handleOnyxUpdateGap(onyxUpdatesFromServer: OnyxEntry<OnyxUpdatesFromSer
142136

143137
// Get the missing Onyx updates from the server and afterwards validate and apply the deferred updates.
144138
// This will trigger recursive calls to "validateAndApplyDeferredUpdates" if there are gaps in the deferred updates.
145-
queryPromise = App.getMissingOnyxUpdates(lastUpdateIDFromClient, previousUpdateIDFromServer).then(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates(clientLastUpdateID));
139+
DeferredOnyxUpdates.setMissingOnyxUpdatesQueryPromise(
140+
App.getMissingOnyxUpdates(lastUpdateIDFromClient, previousUpdateIDFromServer).then(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates(clientLastUpdateID)),
141+
);
146142
}
147143

148-
queryPromise.finally(finalizeUpdatesAndResumeQueue);
144+
DeferredOnyxUpdates.getMissingOnyxUpdatesQueryPromise()?.finally(finalizeUpdatesAndResumeQueue);
149145
}
150146

151147
export default () => {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import Onyx from 'react-native-onyx';
2+
import type {DeferredUpdatesDictionary} from '@libs/actions/OnyxUpdateManager/types';
3+
import * as SequentialQueue from '@libs/Network/SequentialQueue';
4+
import ONYXKEYS from '@src/ONYXKEYS';
5+
import type {OnyxUpdatesFromServer, Response} from '@src/types/onyx';
6+
import {isValidOnyxUpdateFromServer} from '@src/types/onyx/OnyxUpdatesFromServer';
7+
// eslint-disable-next-line import/no-cycle
8+
import * as OnyxUpdateManagerUtils from '.';
9+
10+
let missingOnyxUpdatesQueryPromise: Promise<Response | Response[] | void> | undefined;
11+
let deferredUpdates: DeferredUpdatesDictionary = {};
12+
13+
/**
14+
* Returns the promise that fetches the missing onyx updates
15+
* @returns the promise
16+
*/
17+
function getMissingOnyxUpdatesQueryPromise() {
18+
return missingOnyxUpdatesQueryPromise;
19+
}
20+
21+
/**
22+
* Sets the promise that fetches the missing onyx updates
23+
*/
24+
function setMissingOnyxUpdatesQueryPromise(promise: Promise<Response | Response[] | void>) {
25+
missingOnyxUpdatesQueryPromise = promise;
26+
}
27+
28+
type GetDeferredOnyxUpdatesOptiosn = {
29+
minUpdateID?: number;
30+
};
31+
32+
/**
33+
* Returns the deferred updates that are currently in the queue
34+
* @param minUpdateID An optional minimum update ID to filter the deferred updates by
35+
* @returns
36+
*/
37+
function getUpdates(options?: GetDeferredOnyxUpdatesOptiosn) {
38+
if (options?.minUpdateID == null) {
39+
return deferredUpdates;
40+
}
41+
42+
return Object.entries(deferredUpdates).reduce<DeferredUpdatesDictionary>(
43+
(accUpdates, [lastUpdateID, update]) => ({
44+
...accUpdates,
45+
...(Number(lastUpdateID) > (options.minUpdateID ?? 0) ? {[Number(lastUpdateID)]: update} : {}),
46+
}),
47+
{},
48+
);
49+
}
50+
51+
/**
52+
* Returns a boolean indicating whether the deferred updates queue is empty
53+
* @returns a boolean indicating whether the deferred updates queue is empty
54+
*/
55+
function isEmpty() {
56+
return Object.keys(deferredUpdates).length === 0;
57+
}
58+
59+
/**
60+
* Manually processes and applies the updates from the deferred updates queue. (used e.g. for push notifications)
61+
*/
62+
function process() {
63+
if (missingOnyxUpdatesQueryPromise) {
64+
missingOnyxUpdatesQueryPromise.finally(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates);
65+
}
66+
67+
missingOnyxUpdatesQueryPromise = OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates();
68+
}
69+
70+
type EnqueueDeferredOnyxUpdatesOptions = {
71+
shouldPauseSequentialQueue?: boolean;
72+
};
73+
74+
/**
75+
* Allows adding onyx updates to the deferred updates queue manually.
76+
* @param updates The updates that should be applied (e.g. updates from push notifications)
77+
* @param options additional flags to change the behaviour of this function
78+
*/
79+
function enqueue(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, options?: EnqueueDeferredOnyxUpdatesOptions) {
80+
if (options?.shouldPauseSequentialQueue ?? true) {
81+
SequentialQueue.pause();
82+
}
83+
84+
// We check here if the "updates" param is a single update.
85+
// If so, we only need to insert one update into the deferred updates queue.
86+
if (isValidOnyxUpdateFromServer(updates)) {
87+
const lastUpdateID = Number(updates.lastUpdateID);
88+
deferredUpdates[lastUpdateID] = updates;
89+
} else {
90+
// If the "updates" param is an object, we need to insert multiple updates into the deferred updates queue.
91+
Object.entries(updates).forEach(([lastUpdateIDString, update]) => {
92+
const lastUpdateID = Number(lastUpdateIDString);
93+
if (deferredUpdates[lastUpdateID]) {
94+
return;
95+
}
96+
97+
deferredUpdates[lastUpdateID] = update;
98+
});
99+
}
100+
}
101+
102+
/**
103+
* Adds updates to the deferred updates queue and processes them immediately
104+
* @param updates The updates that should be applied (e.g. updates from push notifications)
105+
*/
106+
function enqueueAndProcess(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, options?: EnqueueDeferredOnyxUpdatesOptions) {
107+
enqueue(updates, options);
108+
process();
109+
}
110+
111+
type ClearDeferredOnyxUpdatesOptions = {
112+
shouldResetGetMissingOnyxUpdatesPromise?: boolean;
113+
shouldUnpauseSequentialQueue?: boolean;
114+
};
115+
116+
/**
117+
* Clears the deferred updates queue and unpauses the SequentialQueue
118+
* @param options additional flags to change the behaviour of this function
119+
*/
120+
function clear(options?: ClearDeferredOnyxUpdatesOptions) {
121+
deferredUpdates = {};
122+
123+
if (options?.shouldResetGetMissingOnyxUpdatesPromise ?? true) {
124+
missingOnyxUpdatesQueryPromise = undefined;
125+
}
126+
127+
if (options?.shouldUnpauseSequentialQueue ?? true) {
128+
Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null);
129+
SequentialQueue.unpause();
130+
}
131+
}
132+
133+
export {getMissingOnyxUpdatesQueryPromise, setMissingOnyxUpdatesQueryPromise, getUpdates, isEmpty, process, enqueue, enqueueAndProcess, clear};

src/libs/actions/OnyxUpdateManager/utils/deferredUpdates.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/libs/actions/OnyxUpdateManager/utils/index.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ import * as App from '@userActions/App';
44
import type {DeferredUpdatesDictionary, DetectGapAndSplitResult} from '@userActions/OnyxUpdateManager/types';
55
import ONYXKEYS from '@src/ONYXKEYS';
66
import {applyUpdates} from './applyUpdates';
7-
import deferredUpdatesProxy from './deferredUpdates';
7+
// eslint-disable-next-line import/no-cycle
8+
import * as DeferredOnyxUpdates from './DeferredOnyxUpdates';
89

910
let lastUpdateIDAppliedToClient = 0;
1011
Onyx.connect({
1112
key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT,
1213
callback: (value) => (lastUpdateIDAppliedToClient = value ?? 0),
1314
});
1415

15-
// In order for the deferred updates to be applied correctly in order,
16-
// we need to check if there are any gaps between deferred updates.
17-
16+
/**
17+
* In order for the deferred updates to be applied correctly in order,
18+
* we need to check if there are any gaps between deferred updates.
19+
* @param updates The deferred updates to be checked for gaps
20+
* @param clientLastUpdateID An optional lastUpdateID passed to use instead of the lastUpdateIDAppliedToClient
21+
* @returns
22+
*/
1823
function detectGapsAndSplit(updates: DeferredUpdatesDictionary, clientLastUpdateID?: number): DetectGapAndSplitResult {
1924
const lastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0;
2025

@@ -75,22 +80,18 @@ function detectGapsAndSplit(updates: DeferredUpdatesDictionary, clientLastUpdate
7580
return {applicableUpdates, updatesAfterGaps, latestMissingUpdateID};
7681
}
7782

78-
// This function will check for gaps in the deferred updates and
79-
// apply the updates in order after the missing updates are fetched and applied
83+
/**
84+
* This function will check for gaps in the deferred updates and
85+
* apply the updates in order after the missing updates are fetched and applied
86+
*/
8087
function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousParams?: {newLastUpdateIDFromClient: number; latestMissingUpdateID: number}): Promise<void> {
8188
const lastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0;
8289

8390
Log.info('[DeferredUpdates] Processing deferred updates', false, {lastUpdateIDFromClient, previousParams});
8491

8592
// We only want to apply deferred updates that are newer than the last update that was applied to the client.
8693
// At this point, the missing updates from "GetMissingOnyxUpdates" have been applied already, so we can safely filter out.
87-
const pendingDeferredUpdates = Object.entries(deferredUpdatesProxy.deferredUpdates).reduce<DeferredUpdatesDictionary>(
88-
(accUpdates, [lastUpdateID, update]) => ({
89-
...accUpdates,
90-
...(Number(lastUpdateID) > lastUpdateIDFromClient ? {[Number(lastUpdateID)]: update} : {}),
91-
}),
92-
{},
93-
);
94+
const pendingDeferredUpdates = DeferredOnyxUpdates.getUpdates({minUpdateID: lastUpdateIDFromClient});
9495

9596
// If there are no remaining deferred updates after filtering out outdated ones,
9697
// we can just unpause the queue and return
@@ -106,7 +107,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa
106107
Log.info('[DeferredUpdates] Gap detected in deferred updates', false, {lastUpdateIDFromClient, latestMissingUpdateID});
107108

108109
return new Promise((resolve, reject) => {
109-
deferredUpdatesProxy.deferredUpdates = {};
110+
DeferredOnyxUpdates.clear({shouldUnpauseSequentialQueue: false, shouldResetGetMissingOnyxUpdatesPromise: false});
110111

111112
applyUpdates(applicableUpdates).then(() => {
112113
// After we have applied the applicable updates, there might have been new deferred updates added.
@@ -116,7 +117,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa
116117

117118
const newLastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0;
118119

119-
deferredUpdatesProxy.deferredUpdates = {...deferredUpdatesProxy.deferredUpdates, ...updatesAfterGaps};
120+
DeferredOnyxUpdates.enqueue(updatesAfterGaps, {shouldPauseSequentialQueue: false});
120121

121122
// If lastUpdateIDAppliedToClient got updated in the meantime, we will just retrigger the validation and application of the current deferred updates.
122123
if (latestMissingUpdateID <= newLastUpdateIDFromClient) {

0 commit comments

Comments
 (0)