realm / realm-core

Core database component for the Realm Mobile Database SDKs
https://realm.io
Apache License 2.0
1.02k stars 165 forks source link

Thread-safe live object accessors #7043

Open astigsen opened 1 year ago

astigsen commented 1 year ago

Since the current object accessors can be updated by the event loop to advance their state, they are not safe to share across threads. This thread-confinement is a huge obstacle to using them in frameworks that transparently shift the workloads between multiple threads. It especially comes up with libraries using Functional Reactive Programming, but it is a generic problem for all users writing multi-threaded code.

We should investigate if we can use thread-local variables to make an accessor that is not thread-confined, but instead always reflect the state of the realm local to the thread from which it is accessed.

finnschiermer commented 1 year ago

When you write "frameworks that transparently shift the workloads between multiple threads" ... is this a setting where multiple threads concurrently access the same object or is it merely non-concurrent access but shifting between threads?

astigsen commented 1 year ago

It could easily be multiple threads concurrently accessing the same object. Even if the frameworks shift between threads in a way that ends up with them being used one-at-a-time, the object may also still concurrently be in active use on the original thread.

finnschiermer commented 1 year ago

But regardless, we can assume that each Transaction instance is specific to a thread? Or is it also used concurrently?

astigsen commented 1 year ago

Yes, the transaction is thread specific. So in effect the object will be seen through the lens of a different transaction depending on which thread it is accessed from.

finnschiermer commented 1 year ago

Ok. For objects, each Transaction can act (already does act) as a thread-specific factory for Obj instances. This is single threaded and mostly lock-free. It just requires us to only hold Obj instances on the stack (they'll be thread specific), never store them elsewhere. We can store instances of Object which can then use a thread-local Transaction to produce the corresponding Obj as needed.

For queries/results it's more complicated. I think we'll need a design with immutable IDs for each query. And a global cache mapping (IDs x version) to a result reference. The IDs will be what can be stored and shared. A result reference is then obtained on demand and may only live on the stack of the thread obtaining it. We'll need this scheme to avoid recomputing the same result for one query at the same version but on a different thread.

jedelbo commented 1 year ago

You must be more specific on which objects you want to be thread-safe. In order to access a given object on some thread we must import the object into a transaction created on that thread. But who will own this transaction? When will it be created and when will it be advanced? And when will it be deleted?

jedelbo commented 1 year ago

An consequence will also be that we need to lock a mutex in every relevant access function.