Skip to content
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

[Audit][Implementation] - avoid re-creating array each time getAllKeys is called #493

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 32 additions & 11 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,12 @@ function get(key) {
/**
* Returns current key names stored in persisted storage
* @private
* @returns {Promise<string[]>}
* @returns {Promise<Set<Key>>}
*/
function getAllKeys() {
// When we've already read stored keys, resolve right away
const storedKeys = cache.getAllKeys();
if (storedKeys.length > 0) {
if (storedKeys.size > 0) {
return Promise.resolve(storedKeys);
}

Expand All @@ -186,7 +186,8 @@ function getAllKeys() {
// Otherwise retrieve the keys from storage and capture a promise to aid concurrent usages
const promise = Storage.getAllKeys().then((keys) => {
cache.setAllKeys(keys);
return keys;
// return the updated set of keys
return cache.getAllKeys();
});

return cache.captureTask(taskName, promise);
Expand Down Expand Up @@ -268,10 +269,18 @@ function tryGetCachedValue(key, mapping = {}) {

// It is possible we haven't loaded all keys yet so we do not know if the
// collection actually exists.
if (allCacheKeys.length === 0) {
if (allCacheKeys.size === 0) {
return;
}
const matchingKeys = _.filter(allCacheKeys, (k) => k.startsWith(key));

const matchingKeys = [];
allCacheKeys.forEach((k) => {
if (!k.startsWith(key)) {
return;
}
matchingKeys.push(k);
});

const values = _.reduce(
matchingKeys,
(finalObject, matchedKey) => {
Expand Down Expand Up @@ -374,7 +383,7 @@ function addToEvictionBlockList(key, connectionID) {
function addAllSafeEvictionKeysToRecentlyAccessedList() {
return getAllKeys().then((keys) => {
_.each(evictionAllowList, (safeEvictionKey) => {
_.each(keys, (key) => {
keys.forEach((key) => {
if (!isKeyMatch(safeEvictionKey, key)) {
return;
}
Expand All @@ -390,8 +399,13 @@ function addAllSafeEvictionKeysToRecentlyAccessedList() {
* @returns {Object}
*/
function getCachedCollection(collectionKey) {
const collectionMemberKeys = _.filter(cache.getAllKeys(), (storedKey) => isCollectionMemberKey(collectionKey, storedKey));

const collectionMemberKeys = [];
cache.getAllKeys().forEach((storedKey) => {
if (!isCollectionMemberKey(collectionKey, storedKey)) {
return;
}
collectionMemberKeys.push(storedKey);
});
return _.reduce(
collectionMemberKeys,
(prev, curr) => {
Expand Down Expand Up @@ -915,7 +929,14 @@ function connect(mapping) {
// We search all the keys in storage to see if any are a "match" for the subscriber we are connecting so that we
// can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
// subscribed to a "collection key" or a single key.
const matchingKeys = _.filter(keys, (key) => isKeyMatch(mapping.key, key));

const matchingKeys = [];
keys.forEach((key) => {
if (!isKeyMatch(mapping.key, key)) {
return;
}
matchingKeys.push(key);
});

// If the key being connected to does not exist we initialize the value with null. For subscribers that connected
// directly via connect() they will simply get a null value sent to them without any information about which key matched
Expand Down Expand Up @@ -1395,7 +1416,7 @@ function clear(keysToPreserve = []) {
// status, or activeClients need to remain in Onyx even when signed out)
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
// to null would cause unknown behavior)
_.each(keys, (key) => {
keys.forEach((key) => {
const isKeyToPreserve = _.contains(keysToPreserve, key);
const isDefaultKey = _.has(defaultKeyStates, key);

Expand Down Expand Up @@ -1502,7 +1523,7 @@ function mergeCollection(collectionKey, collection) {
return true;
})
.keys()
.partition((key) => persistedKeys.includes(key))
.partition((key) => persistedKeys.has(key))
.value();

const existingKeyCollection = _.pick(collection, existingKeys);
Expand Down
4 changes: 2 additions & 2 deletions lib/OnyxCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class OnyxCache {
}

/** Get all the storage keys */
getAllKeys(): Key[] {
return Array.from(this.storageKeys);
getAllKeys(): Set<Key> {
return this.storageKeys;
}

/**
Expand Down
18 changes: 9 additions & 9 deletions tests/unit/onyxCacheTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Onyx', () => {
const allKeys = cache.getAllKeys();

// Then the result should be empty
expect(allKeys).toEqual([]);
expect(allKeys).toEqual(new Set());
});

it('Should keep storage keys', () => {
Expand All @@ -37,7 +37,7 @@ describe('Onyx', () => {

// Then the keys should be stored in cache
const allKeys = cache.getAllKeys();
expect(allKeys).toEqual(['mockKey', 'mockKey2', 'mockKey3']);
expect(allKeys).toEqual(new Set(['mockKey', 'mockKey2', 'mockKey3']));
});

it('Should keep storage keys even when no values are provided', () => {
Expand All @@ -48,7 +48,7 @@ describe('Onyx', () => {

// Then the keys should be stored in cache
const allKeys = cache.getAllKeys();
expect(allKeys).toEqual(['mockKey', 'mockKey2', 'mockKey3']);
expect(allKeys).toEqual(new Set(['mockKey', 'mockKey2', 'mockKey3']));
});

it('Should not store duplicate keys', () => {
Expand All @@ -62,7 +62,7 @@ describe('Onyx', () => {

// Then getAllKeys should not include a duplicate value
const allKeys = cache.getAllKeys();
expect(allKeys).toEqual(['mockKey', 'mockKey2', 'mockKey3']);
expect(allKeys).toEqual(new Set(['mockKey', 'mockKey2', 'mockKey3']));
});
});

Expand Down Expand Up @@ -121,7 +121,7 @@ describe('Onyx', () => {
expect(cache.hasCacheForKey('mockKey')).toBe(false);

// Then but a key should be available
expect(cache.getAllKeys()).toEqual(expect.arrayContaining(['mockKey']));
expect(cache.getAllKeys()).toEqual(new Set(['mockKey']));
});

it('Should not make duplicate keys', () => {
Expand All @@ -135,7 +135,7 @@ describe('Onyx', () => {

// Then getAllKeys should not include a duplicate value
const allKeys = cache.getAllKeys();
expect(allKeys).toEqual(['mockKey', 'mockKey2']);
expect(allKeys).toEqual(new Set(['mockKey', 'mockKey2']));
});
});

Expand All @@ -158,7 +158,7 @@ describe('Onyx', () => {
cache.set('mockKey', {value: 'mockValue'});

// Then but a key should be available
expect(cache.getAllKeys()).toEqual(expect.arrayContaining(['mockKey']));
expect(cache.getAllKeys()).toEqual(new Set(['mockKey']));
});

it('Should overwrite existing cache items for the Given key', () => {
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('Onyx', () => {
// Then a value should not be available in cache
expect(cache.hasCacheForKey('mockKey')).toBe(false);
expect(cache.getValue('mockKey')).not.toBeDefined();
expect(cache.getAllKeys('mockKey').includes('mockKey')).toBe(false);
expect(cache.getAllKeys('mockKey').has('mockKey')).toBe(false);
});
});

Expand Down Expand Up @@ -336,7 +336,7 @@ describe('Onyx', () => {
});

// Then getAllStorage keys should return updated storage keys
expect(cache.getAllKeys()).toEqual(['mockKey', 'mockKey2', 'mockKey3', 'mockKey4']);
expect(cache.getAllKeys()).toEqual(new Set(['mockKey', 'mockKey2', 'mockKey3', 'mockKey4']));
});

it('Should throw if called with anything that is not an object', () => {
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/onyxTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ describe('Onyx', () => {
Onyx.set(ONYX_KEYS.OTHER_TEST, 42)
.then(() => Onyx.getAllKeys())
.then((keys) => {
expect(keys.includes(ONYX_KEYS.OTHER_TEST)).toBe(true);
expect(keys.has(ONYX_KEYS.OTHER_TEST)).toBe(true);
return Onyx.set(ONYX_KEYS.OTHER_TEST, null);
})
.then(() => {
// Checks if cache value is removed.
expect(cache.getAllKeys().length).toBe(0);
expect(cache.getAllKeys().size).toBe(0);

// When cache keys length is 0, we fetch the keys from storage.
return Onyx.getAllKeys();
})
.then((keys) => {
expect(keys.includes(ONYX_KEYS.OTHER_TEST)).toBe(false);
expect(keys.has(ONYX_KEYS.OTHER_TEST)).toBe(false);
}));

it('should set a simple key', () => {
Expand Down
Loading