- Notifications
You must be signed in to change notification settings - Fork1.1k
Description
Good day!
We have a React-App where we use RxDB to store user related information. This means each user has its own db (we use the userID inside the db name). The RxDB is initialized and cleaned up inside aneffect
. A simple example code looks like this:
const[rxdbInstance,setRxdbInstance]=useState(null);useEffect(()=>{constabortController=newAbortController();constrxdbInstancePromise=createRxDatabase({name:`prefix-${userId}-suffix`}).then((rxdbInstance)=>{if(!abortController.signal.aborted){setRxdbInstance(rxdbInstance);}}).catch(()=>{if(!abortController.signal.aborted){setRxdbInstance(null);}});return()=>{abortController.abort();rxdbInstancePromise.then((rxdbInstance)=>rxdbInstance.destroy());}},[userId])
This code runs perfectly fine and each rxdb instance is cleaned up in case theuserId
changes. The problem here is that initialization and destruction isasync
. If theuserId
would quickly change like this:
- user-a
- user-b
- user-a
Then we would encounter aDB8
error for the second initialization ofuser-a
because the first initialization might not be destroyed yet. This scenario is theoretical inproduction
but very real indevelopment
because React runs effects twice because of its strict mode.
This behavior is ahuge DX issue for us. To fix this issue we implemented a tracking system where we always wait for a rxdb with the same name and storage to destroy before a new one can be intialized, but for some time now we observe our users in production getting this error right after deployments.
Going back to what theDB8
error actually means and tries to solve: Having two different instances of the same RXDB being initializedand being used at the same time. This would be easy to track if everything would be snychronous but is harder if initialization and destruction are async, because it actually takes time until one instance is ready / destroyed.
I would have three suggestions to improve this behavior but keep the semantics in place:
- The first solution would be to again allow the option
ignoreDuplicate
being true without the need of thedev-mode
plugin. (personally not a fan of this solution, although its the simplest to implement) - The
DB8
error doesn't happen anymore on initialization but only if the instance actually being used. (So having two instances at the same time is fine, but only the instance which was initialized later can actually be used without this error happening) - Use an
AbortSignal
to identify whether a instance is "active". ThecreateRxDatabase
could add an additional fieldsignal
which is an abort signal. This signal is there for keeping track of whether the instance is active or should be considered destroyed. In this case the instance can be destroyed via thedestroy
function or thesignal
. The mechanism of abort signals is synchronous so the instance could be flagged asinactive
even before its initialization is fully complete, so all initializations after that would not trigger theDB8
error.
Of those three solutions I'm personally prefering the thrid one, since its the (in my opinion) most elegant one. To elaborate a bit further, here is some example code:
constrxdb1AbortController=newAbortController();constrxdb1Promise=createRxDatabase({name:'user-a',signal:rxdb1AbortController.signal});// rxdb1 is not finished initializing yet, since the promise is not awaitedrxdb1AbortController.abort();constrxdb2AbortController=newAbortController();// rxdb1 is likely still not finished initializing// but since its signal aborted we know that rxdb1 can't be used anymore so the DB8 should not be thrown hereconstrxdb2Promise=createRxDatabase({name:'user-a',signal:rxdb2AbortController.signal});constrxdb1=awaitrxdb1Promise;// rxdb1 is destroyed right awayconstrxdb2=awaitrxdb2Promise;// rxdb2 is ready
This solution would improve DX considerably and be compatilbe with the web platform which is moving more and more in the direction ofAbortControllers
for controling async tasks.