-
Notifications
You must be signed in to change notification settings - Fork 4.7k
[core data] State locks for concurrency control #26389
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
Conversation
|
Size Change: +2.52 kB (0%) Total Size: 1.21 MB
ℹ️ View Unchanged
|
|
I'm tinkering with store-based storage, but any feedback on the direction is warmly welcome! |
noisysocks
left a comment
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.
This is really cool! Not as nightmarishly complex as I had imagined it would be 🙂
Three suggestions—all of which I'm sure you're already working on:
- Please update it to use redux state instead of a global variable.
- Please add unit tests.
- I was only able to grok how the code worked after I had read this PR's (excellent) description, so I suggest moving the "plumbing" functions (
acquireLock,releaseLock, etc.) into their own file with a giant/** */at the top containing what you wrote there.
270e760 to
9501daf
Compare
|
I will solve the conflicts tomorrow. This PR lacks comments, but hopefully all tests will pass. CC @youknowriad @mcsf @draganescu @noisysocks - let's discuss some more! |
b7befbc to
9287aa3
Compare
|
These e2e test failures are unsettling - interestingly all the tests pass in my local environment 🤔 Edit: These failures are from tests missing something like |
9287aa3 to
73e23df
Compare
|
Tests failures are:
The first one is unrelated (#26527), the second one I'm not sure - anyone familiar with that one? |
a141486 to
7756b49
Compare
|
Please ignore test failures for the purposes of reviewing this PR. Rebasing every day is tedious so I'm just giving up on it until it's closer to being merged. Also:
|
…ent (cousin? :D) held a conflicting lock
Restore expectation of 5 assertions
7756b49 to
ea37361
Compare

An implementation of serial processing idea discussed in #26325
Testing instructions:
Details:
The problem is that operations mutating the store (resolvers, saveEntityRecord) interfere with each other by changing the state used by other "concurrent" operations. My solution is to enable these operations to lock parts of the state tree that are expected to remain constant for the duration of the operation. For example
saveEntityRecord("postType", "book", 3)would acquire a lock on a book with id=3 and nothing else.More generally, interfering store operations should run sequentially. Each operation acquires a lock scoped to its state dependencies. If a lock cannot be acquired, the operation is delayed until a lock can be acquired. Locks are either exclusive or shared. If all operations request a shared lock, everything is executed concurrently. If any operation requests an exclusive lock, is it run only after all other locks (shared or exclusive) acting on the same
scopeare released.The most complex part of this PR is the concept of
scope. Locks are stored in a tree. Each node in the tree looks like this:Locks are stored like this:
A more complete, but still simplified, tree looks like this:
Let's imagine we want to fetch a list of books. One way to control concurrency would be to acquire a shared lock on
book. The lock will be granted once there are no exclusive locks on:bookitselfroot)book > 1,book > 2)In the case above we're good to grab the lock, let's do it then:
Let's imagine that another fetch was triggered to get a filtered list of entities. By checking the criteria above, it's okay to grab a shared lock on books so now we have two:
While these are running, the user triggered an update of book id=1. Now an update operation requests an exclusive lock. It will be able to acquire one once there are no locks at all on:
book > 1itselfroot,book)Since there are two shared locks on
book, the operation is delayed until both of them are released. Once that happens, an exclusive lock is granted on a specific book:If any fetch operation is triggered now, it will attempt to acquire a shared lock on
book. Since one ofbookdescendants holds an exclusive lock, fetch must wait until that lock is released.This structure makes it quite easy to control concurrent reads/writes with, as I believe, any granularity. For example, if we wanted to make sure two fetch operations may run only if their
queryis different, each could acquire two locks: a shared lock onbookto prevent clashing with writes, and an exclusive lock on an unrelated branch such asfetchByQuery > [query].Other notes
This API is meant to be generic so that it could be re-used by any other package that needs state synchronization primitives.
cc @noisysocks