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

Revert "Fix concurrent writes in the update operation" #397

Merged
merged 1 commit into from
Oct 10, 2023
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dist/

# IDEs
.idea
.vscode

# Decrypted private key we do not want to commit
.github/OSBotify-private-key.asc
67 changes: 26 additions & 41 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -1422,42 +1422,6 @@ function mergeCollection(collectionKey, collection) {
});
}

/**
* Internal recursive function to execute the functions in the correct order
*
* @param {Array} data An array of objects with shape {onyxMethod: oneOf('set', 'merge', 'mergeCollection', 'multiSet', 'clear'), key: string, value: *}
* @returns {Promise<Void>}
*/
function innerUpdate(data) {
if (data.length === 0) {
return Promise.resolve();
}

const {onyxMethod, key, value} = data.shift();
let promise = Promise.resolve();
switch (onyxMethod) {
case METHOD.SET:
promise = set(key, value);
break;
case METHOD.MERGE:
promise = merge(key, value);
break;
case METHOD.MERGE_COLLECTION:
promise = mergeCollection(key, value);
break;
case METHOD.MULTI_SET:
promise = multiSet(value);
break;
case METHOD.CLEAR:
promise = clear();
break;
default:
break;
}

return promise.then(() => innerUpdate(data));
}

/**
* Insert API responses and lifecycle data into Onyx
*
Expand All @@ -1466,9 +1430,8 @@ function innerUpdate(data) {
*/
function update(data) {
// First, validate the Onyx object is in the format we expect
const validMethods = [METHOD.CLEAR, METHOD.SET, METHOD.MERGE, METHOD.MERGE_COLLECTION, METHOD.MULTI_SET];
_.each(data, ({onyxMethod, key, value}) => {
if (!_.contains(validMethods, onyxMethod)) {
if (!_.contains([METHOD.CLEAR, METHOD.SET, METHOD.MERGE, METHOD.MERGE_COLLECTION, METHOD.MULTI_SET], onyxMethod)) {
throw new Error(`Invalid onyxMethod ${onyxMethod} in Onyx update.`);
}
if (onyxMethod === METHOD.MULTI_SET) {
Expand All @@ -1481,10 +1444,32 @@ function update(data) {
}
});

// Put clear operation on top
data.sort(a => (a.onyxMethod === METHOD.CLEAR ? -1 : 1));
const promises = [];
let clearPromise = Promise.resolve();

_.each(data, ({onyxMethod, key, value}) => {
switch (onyxMethod) {
case METHOD.SET:
promises.push(() => set(key, value));
break;
case METHOD.MERGE:
promises.push(() => merge(key, value));
break;
case METHOD.MERGE_COLLECTION:
promises.push(() => mergeCollection(key, value));
break;
case METHOD.MULTI_SET:
promises.push(() => multiSet(value));
break;
case METHOD.CLEAR:
clearPromise = clear();
break;
default:
break;
}
});

return innerUpdate(data);
return clearPromise.then(() => Promise.all(_.map(promises, p => p())));
}

/**
Expand Down
71 changes: 3 additions & 68 deletions tests/unit/onyxTest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import _ from 'underscore';
import Onyx from '../../lib';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import Storage from '../../lib/storage';

const ONYX_KEYS = {
TEST_KEY: 'test',
Expand Down Expand Up @@ -919,9 +918,9 @@ describe('Onyx', () => {
{onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}}},
])
.then(() => {
expect(collectionCallback).toHaveBeenNthCalledWith(2, {[itemKey]: {a: 'a'}});
expect(testCallback).toHaveBeenNthCalledWith(2, 'taco', ONYX_KEYS.TEST_KEY);
expect(otherTestCallback).toHaveBeenNthCalledWith(2, 'pizza', ONYX_KEYS.OTHER_TEST);
expect(collectionCallback).toHaveBeenNthCalledWith(1, {[itemKey]: {a: 'a'}});
expect(testCallback).toHaveBeenNthCalledWith(1, 'taco', ONYX_KEYS.TEST_KEY);
expect(otherTestCallback).toHaveBeenNthCalledWith(1, 'pizza', ONYX_KEYS.OTHER_TEST);
Onyx.disconnect(connectionIDs);
});
});
Expand Down Expand Up @@ -980,68 +979,4 @@ describe('Onyx', () => {
});
});
});

it('should persist data in the correct order', () => {
const key = `${ONYX_KEYS.TEST_KEY}123`;
const callback = jest.fn();
connectionID = Onyx.connect({
key,
initWithStoredValues: false,
callback,
});

return waitForPromisesToResolve()
.then(() => Onyx.update([
{
onyxMethod: 'set',
key,
value: 'one',
},
{
onyxMethod: 'merge',
key,
value: 'two',
},
{
onyxMethod: 'set',
key,
value: 'three',
},
]))
.then(() => Storage.getItem(key))
.then((value) => {
expect(callback).toHaveBeenNthCalledWith(1, 'one', key);
expect(callback).toHaveBeenNthCalledWith(2, 'two', key);
expect(callback).toHaveBeenNthCalledWith(3, 'three', key);
expect(value).toBe('three');
});
});

it('should persist data in the correct order', () => {
const key = `${ONYX_KEYS.TEST_KEY}123`;
const callback = jest.fn();
connectionID = Onyx.connect({
key,
initWithStoredValues: false,
callback,
});

return waitForPromisesToResolve()
.then(() => Onyx.update([
{
onyxMethod: 'set',
key,
value: 'one',
},
{
onyxMethod: 'clear',
},

]))
.then(() => Storage.getItem(key))
.then((value) => {
expect(callback).toHaveBeenNthCalledWith(1, 'one', key);
expect(value).toBe('one');
});
});
});
Loading