-
Notifications
You must be signed in to change notification settings - Fork 73
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
Fix concurrent writes in the update operation #384
Changes from 3 commits
a941bd8
e26d5b5
8167a91
944bfac
24971b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1392,6 +1392,36 @@ function mergeCollection(collectionKey, collection) { | |
}); | ||
} | ||
|
||
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; | ||
} | ||
Comment on lines
+1408
to
+1426
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this change possibly led to some regressions spotted in this PR and discussed in this Slack thread that updated the Onyx version |
||
|
||
return promise.then(() => innerUpdate(data)); | ||
} | ||
|
||
/** | ||
* Insert API responses and lifecycle data into Onyx | ||
* | ||
|
@@ -1414,32 +1444,7 @@ function update(data) { | |
} | ||
}); | ||
|
||
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 clearPromise.then(() => Promise.all(_.map(promises, p => p()))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see we are undoing the previous logic to move the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, probably not. I'll take care of it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure making sure clear runs first is a desired behavior but I guess now it is too late to change it, other parts of the system might rely on it. I added sorting to the list to make sure clear operations get executed first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I agree it is a bit of a weird hack. But changing it could have unexpected consequences. Thanks for updating. |
||
return innerUpdate(data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a data point, I tried the proposed solution by Marc which would try to resolve earlier while keeping the writes async, this however is not correct and broke a bunch of other tests. |
||
} | ||
|
||
/** | ||
|
@@ -1534,6 +1539,7 @@ const Onyx = { | |
connect, | ||
disconnect, | ||
set, | ||
get, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was pretty import to avoid exporting this so that people don't get lazy in App and start using it all over the place (which encourages a lot of chaining calls). It looks like you're exporting it for use in the tests and I would like to propose these alternatives:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I saw Onyx.connect has caching implemented, which might not be what is actually persisted on the DB. I was about to directly query the DB which is at the end of the day what really matters. I think this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of querying the DB directly if that's possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
multiSet, | ||
merge, | ||
mergeCollection, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -899,9 +899,9 @@ describe('Onyx', () => { | |
{onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}}}, | ||
]) | ||
.then(() => { | ||
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); | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This previously was receiving the write updates before the hydration callback, but now that the write operations take a little longer the order is correct. |
||
Onyx.disconnect(connectionIDs); | ||
}); | ||
}); | ||
|
@@ -960,4 +960,41 @@ describe('Onyx', () => { | |
}); | ||
}); | ||
}); | ||
|
||
it('should write data in order', () => { | ||
const key = `${ONYX_KEYS.TEST_KEY}123`; | ||
connectionID = Onyx.connect({ | ||
key, | ||
initWithStoredValues: false, | ||
callback: () => null, | ||
}); | ||
|
||
return waitForPromisesToResolve() | ||
.then(() => { | ||
// Given the initial Onyx state: {test: true, otherTest: {test1: 'test1'}} | ||
Onyx.set(key, true); | ||
return waitForPromisesToResolve(); | ||
}) | ||
.then(() => Onyx.update([ | ||
{ | ||
onyxMethod: 'set', | ||
key, | ||
value: 'one', | ||
}, | ||
{ | ||
onyxMethod: 'merge', | ||
key, | ||
value: 'two', | ||
}, | ||
{ | ||
onyxMethod: 'set', | ||
key, | ||
value: 'three', | ||
}, | ||
])) | ||
.then(() => Onyx.get(key)) | ||
.then((value) => { | ||
expect(value).toBe('three'); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add JSDoc