Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
df729c3
Update evictStorageAndRetry to not evict the data if error isn't stor…
VickyStash Oct 28, 2025
6fc70ed
Minor improvement and test fix
VickyStash Oct 28, 2025
c4d29dc
Applied feedback
VickyStash Oct 28, 2025
67bd9d4
Add comments
VickyStash Oct 29, 2025
a8fbd85
Add a simple limitation of operations retries
VickyStash Oct 29, 2025
42f44fb
Merge branch 'main' into VickyStash/bugfix/update-eviction-rules
VickyStash Oct 29, 2025
3bfb0e5
re-run checks
VickyStash Oct 29, 2025
9db02f2
Add internal functions for retries
VickyStash Oct 30, 2025
c146abf
Update mergeCollectionWithPatches
VickyStash Oct 30, 2025
5a6d696
Update partialSetCollection
VickyStash Oct 30, 2025
23e996e
Rename const
VickyStash Oct 30, 2025
1bd718a
Minor clean-up
VickyStash Oct 30, 2025
b57b22a
Rename const
VickyStash Oct 30, 2025
5e0c4ad
Move setWithRetry, multiSetWithRetry, setCollectionWithRetry to OnyxU…
VickyStash Oct 30, 2025
7f0bf74
Minor code improvements
VickyStash Oct 31, 2025
ca326b9
Add unit tests
VickyStash Oct 31, 2025
c55cc64
Update the docs
VickyStash Oct 31, 2025
7da26ae
Adjust params passed to the retry function
VickyStash Nov 3, 2025
f5e8b4b
Apply feedback
VickyStash Nov 3, 2025
fa98ffb
Merge branch 'main' into VickyStash/bugfix/update-eviction-rules
VickyStash Nov 3, 2025
3a6e61d
Add changes from main
VickyStash Nov 3, 2025
8ebfc39
Add logging when max retries are reached
VickyStash Nov 3, 2025
fddfe2d
Re-run checks
VickyStash Nov 3, 2025
5b3fcd1
Merge branch 'main' into VickyStash/bugfix/update-eviction-rules
VickyStash Nov 5, 2025
fa363e8
TS adjustments after merging main
VickyStash Nov 5, 2025
447f9e3
Minor improvement
VickyStash Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,11 @@ subscriber callbacks receive the data in a different format than they normally e
<dt><a href="#remove">remove()</a></dt>
<dd><p>Remove a key from Onyx and update the subscribers</p>
</dd>
<dt><a href="#evictStorageAndRetry">evictStorageAndRetry()</a></dt>
<dd><p>If we fail to set or merge we must handle this by
evicting some data from Onyx and then retrying to do
whatever it is we attempted to do.</p>
<dt><a href="#retryOperation">retryOperation()</a></dt>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you regenerate these docs after your recent changes? I don't think retryOperation() is exposed anywhere publicly, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tgolen I've re-generated it one more time.
Yeah, it's not public! This file is also API-INTERNAL (describes internal methods)

<dd><p>Handles storage operation failures based on the error type:
- Storage capacity errors: evicts data and retries the operation
- Invalid data errors: logs an alert and throws an error
- Other errors: retries the operation</p>
</dd>
<dt><a href="#broadcastUpdate">broadcastUpdate()</a></dt>
<dd><p>Notifies subscribers and writes current value to cache</p>
Expand Down Expand Up @@ -405,13 +406,14 @@ subscriber callbacks receive the data in a different format than they normally e
## remove()
Remove a key from Onyx and update the subscribers

**Kind**: global function
<a name="evictStorageAndRetry"></a>
**Kind**: global function
<a name="retryOperation"></a>

## evictStorageAndRetry()
If we fail to set or merge we must handle this by
evicting some data from Onyx and then retrying to do
whatever it is we attempted to do.
## retryOperation()
Handles storage operation failures based on the error type:
- Storage capacity errors: evicts data and retries the operation
- Invalid data errors: logs an alert and throws an error
- Other errors: retries the operation

**Kind**: global function
<a name="broadcastUpdate"></a>
Expand Down
6 changes: 3 additions & 3 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function set<TKey extends OnyxKey>(key: TKey, value: OnyxSetInput<TKey>, options
}

return Storage.setItem(key, valueWithoutNestedNullValues)
.catch((error) => OnyxUtils.evictStorageAndRetry(error, set, key, valueWithoutNestedNullValues))
.catch((error) => OnyxUtils.retryOperation(error, set, key, valueWithoutNestedNullValues))
.then(() => {
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET, key, valueWithoutNestedNullValues);
return updatePromise;
Expand Down Expand Up @@ -258,7 +258,7 @@ function multiSet(data: OnyxMultiSetInput): Promise<void> {
});

return Storage.multiSet(keyValuePairsToSet)
.catch((error) => OnyxUtils.evictStorageAndRetry(error, multiSet, newData))
.catch((error) => OnyxUtils.retryOperation(error, multiSet, newData))
.then(() => {
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, newData);
return Promise.all(updatePromises);
Expand Down Expand Up @@ -702,7 +702,7 @@ function setCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey
const updatePromise = OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);

return Storage.multiSet(keyValuePairs)
.catch((error) => OnyxUtils.evictStorageAndRetry(error, setCollection, collectionKey, collection))
.catch((error) => OnyxUtils.retryOperation(error, setCollection, collectionKey, collection))
.then(() => {
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET_COLLECTION, undefined, mutableCollection);
return updatePromise;
Expand Down
29 changes: 21 additions & 8 deletions lib/OnyxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const METHOD = {
CLEAR: 'clear',
} as const;

const IDB_STORAGE_ERRORS = ['quotaexceedederror'] as const;
const SQL_STORAGE_ERRORS = ['database or disk is full', 'disk I/O error', 'out of memory'] as const;
const STORAGE_ERRORS = [...IDB_STORAGE_ERRORS, ...SQL_STORAGE_ERRORS];

type OnyxMethod = ValueOf<typeof METHOD>;

// Key/value store of Onyx key and arrays of values to merge
Expand Down Expand Up @@ -858,11 +862,12 @@ function reportStorageQuota(): Promise<void> {
}

/**
* If we fail to set or merge we must handle this by
* evicting some data from Onyx and then retrying to do
* whatever it is we attempted to do.
* Handles storage operation failures based on the error type:
* - Storage capacity errors: evicts data and retries the operation
* - Invalid data errors: logs an alert and throws an error
* - Other errors: retries the operation
*/
function evictStorageAndRetry<TMethod extends typeof Onyx.set | typeof Onyx.multiSet | typeof Onyx.mergeCollection | typeof Onyx.setCollection>(
function retryOperation<TMethod extends typeof Onyx.set | typeof Onyx.multiSet | typeof Onyx.mergeCollection | typeof Onyx.setCollection>(
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: Do you think it would be feasible to add a type backing TMethod that requires an onyx method to have an optional retryAttempt parameter on the last position? 👀

error: Error,
onyxMethod: TMethod,
...args: Parameters<TMethod>
Expand All @@ -874,6 +879,14 @@ function evictStorageAndRetry<TMethod extends typeof Onyx.set | typeof Onyx.mult
throw error;
}

const errorMessage = error?.message?.toLowerCase?.();
const isStorageCapacityError = STORAGE_ERRORS.some((storageError) => storageError === error?.name?.toLowerCase() || errorMessage?.includes(storageError));

if (!isStorageCapacityError) {
// @ts-expect-error No overload matches this call.
return onyxMethod(...args);
}

// Find the first key that we can remove that has no subscribers in our blocklist
const keyForRemoval = cache.getKeyForEviction();
if (!keyForRemoval) {
Expand Down Expand Up @@ -1341,7 +1354,7 @@ function mergeCollectionWithPatches<TKey extends CollectionKeyBase, TMap>(
});

return Promise.all(promises)
.catch((error) => evictStorageAndRetry(error, mergeCollectionWithPatches, collectionKey, resultCollection))
.catch((error) => retryOperation(error, mergeCollectionWithPatches, collectionKey, resultCollection))
.then(() => {
sendActionToDevTools(METHOD.MERGE_COLLECTION, undefined, resultCollection);
return promiseUpdate;
Expand Down Expand Up @@ -1396,7 +1409,7 @@ function partialSetCollection<TKey extends CollectionKeyBase, TMap>(collectionKe
const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);

return Storage.multiSet(keyValuePairs)
.catch((error) => evictStorageAndRetry(error, partialSetCollection, collectionKey, collection))
.catch((error) => retryOperation(error, partialSetCollection, collectionKey, collection))
.then(() => {
sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
return updatePromise;
Expand Down Expand Up @@ -1452,7 +1465,7 @@ const OnyxUtils = {
scheduleNotifyCollectionSubscribers,
remove,
reportStorageQuota,
evictStorageAndRetry,
retryOperation,
broadcastUpdate,
hasPendingMergeForKey,
prepareKeyValuePairsForStorage,
Expand Down Expand Up @@ -1512,7 +1525,7 @@ GlobalSettings.addGlobalSettingsChangeListener(({enablePerformanceMetrics}) => {
// @ts-expect-error Reassign
reportStorageQuota = decorateWithMetrics(reportStorageQuota, 'OnyxUtils.reportStorageQuota');
// @ts-expect-error Complex type signature
evictStorageAndRetry = decorateWithMetrics(evictStorageAndRetry, 'OnyxUtils.evictStorageAndRetry');
retryOperation = decorateWithMetrics(retryOperation, 'OnyxUtils.retryOperation');
// @ts-expect-error Reassign
broadcastUpdate = decorateWithMetrics(broadcastUpdate, 'OnyxUtils.broadcastUpdate');
// @ts-expect-error Reassign
Expand Down
4 changes: 2 additions & 2 deletions tests/perf-test/OnyxUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,12 +502,12 @@ describe('OnyxUtils', () => {
});
});

describe('evictStorageAndRetry', () => {
describe('retryOperation', () => {
test('one call', async () => {
const error = new Error();
const onyxMethod = jest.fn() as typeof Onyx.set;

await measureAsyncFunction(() => OnyxUtils.evictStorageAndRetry(error, onyxMethod, '', null), {
await measureAsyncFunction(() => OnyxUtils.retryOperation(error, onyxMethod, '', null), {
beforeEach: async () => {
mockedReportActionsKeys.forEach((key) => OnyxCache.addLastAccessedKey(key, false));
OnyxCache.addLastAccessedKey(`${ONYXKEYS.COLLECTION.EVICTABLE_TEST_KEY}1`, false);
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/cacheEvictionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test('Cache eviction', () => {
const setItemMock = jest.fn(originalSetItem).mockImplementationOnce(
() =>
new Promise((_resolve, reject) => {
reject();
reject(new Error('out of memory'));
}),
);
StorageMock.setItem = setItemMock;
Expand Down