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

RealmProvider should have an option like keepRealmOpen #6023

Closed
djgamarra opened this issue Jul 31, 2023 · 10 comments · Fixed by #6088
Closed

RealmProvider should have an option like keepRealmOpen #6023

djgamarra opened this issue Jul 31, 2023 · 10 comments · Fixed by #6088

Comments

@djgamarra
Copy link

djgamarra commented Jul 31, 2023

Problem

Hi!

I apologize if this can be achieved with the current options that RealmProvider has, but I could not find how to make my use case to work with it. Please let me know if there is a way to get it working.

I'm using:
React Native 0.72.3
Realm: 11.10.1
@realm/react 0.5.1

This is my problem:

I have two realms in my app, one is public, the other is private (requires a logged user), none of them uses sync. So, I'm using createRealmContext. I have some RN headless tasks that will use both realms, these tasks can run on background or foreground, but when I tried to use the realms sometimes I was getting the error Cannot access a realm that is closed. I could not find a way to keep my realm instances independent, so that if the RealmProvider closes it the one being used by the headless task keeps open. What I had to do was to patch-package. This is what I changed in node_modules/@realm/react/dist/index.js:

--- a/node_modules/@realm/react/dist/index.js
+++ b/node_modules/@realm/react/dist/index.js
@@ -665,26 +667,6 @@ function createRealmProvider(realmConfig, RealmContext) {
                 realmRef.current = realm;
             }
         }, [realm]);
-        useEffect(() => {
-            const realmRef = currentRealm.current;
-            // Check if we currently have an open Realm. If we do not (i.e. it is the first
-            // render, or the Realm has been closed due to a config change), then we
-            // need to open a new Realm.
-            const shouldInitRealm = realmRef === null;
-            const initRealm = async () => {
-                const openRealm = await Realm.open(configuration.current);
-                setRealm(openRealm);
-            };
-            if (shouldInitRealm) {
-                initRealm().catch(console.error);
-            }
-            return () => {
-                if (realm) {
-                    realm.close();
-                    setRealm(null);
-                }
-            };
-        }, [configVersion, realm, setRealm]);
         if (!realm) {
             if (typeof Fallback === "function") {
                 return React.createElement(Fallback, null);

NOTE: This works for me because I'm not changing the Realm config on the fly. Also, I think it can lead to memory leaks.

Solution

One solution can be to let the instances independent, but with a prop in the RealmProvider that prevents the realm from being closed.

Alternatives

Another solution can be to let us pass to the createRealmContext the realm instance so that we can have a unique instance of the realm in the entire app, for headless tasks or just code outside of the React component tree and for the hooks provided.

How important is this improvement for you?

Would be a major improvement

Feature would mainly be used with

Local Database only

@takameyer
Copy link
Contributor

takameyer commented Aug 2, 2023

@djgamarra In the near future we will be reimplementing how realm open and closures occur. Currently, when one opens the same realm multiple times, a single call to realm.close() will close all the open realms. This would introduce an instance counter and only close the open realm when all instances have requested a close or have gone out of scope. This should fix the issue you are having.

That being said, the solution you have written would be pretty straightforward to implement, so i'll put it up for discussion within the team. Thanks for the request!

@Acetyld
Copy link

Acetyld commented Aug 11, 2023

Perfect, this is exactly the problem i am having right now.
I am using a background worker or a redux action and i open realm in their and then close it, now indeed it closes all realm instances.

Is there a way around .close closing both? If i open the realm with exact same config i want that one to run independent

   const realm = new Realm(realmConfig);
      try {
        realm.write(() => {
          realm.create(
            User.schema.name,
            User.add(realm, userResponse.data),
            UpdateMode.Modified,
          );
          realm.create(
            Client.schema.name,
            Client.add(clientResponse.data),
            UpdateMode.Modified,
          );
        });
      } finally {
        // Close the Realm instance after writing
        realm.close();
      }

@djgamarra
Copy link
Author

@Acetyld this is exactly what happens when we close a Realm, it closes all the instances of the same Realm. What I finally did was to have two instances, the first is the one that RealmProvider creates, and the other one is like a singleton instance for the rest of the code outside of the React tree, I think it is okay to just have that instance (the second one) open for the entire app lifecycle. Just make sure the RealmProvider will not close it. For example if there is a background worker running while the app is open and we close it, RealmProvider closes the instance automatically and the instance in our Worker will throw an error if we use it.

By now, I get it working using patch-package. This is my patch (by the way, it solves this issue and another one I discussed in #6021):
patches/@realm+react+0.5.1.patch

diff --git a/node_modules/@realm/react/dist/index.js b/node_modules/@realm/react/dist/index.js
index 11274f5..558c916 100644
--- a/node_modules/@realm/react/dist/index.js
+++ b/node_modules/@realm/react/dist/index.js
@@ -356,6 +356,8 @@ function createUseObject(useRealm) {
                     updatedRef,
                 });
                 originalObjectRef.current = originalObject;
+                primaryKeyRef.current = primaryKey;
+                updatedRef.current = true;
             }
             return cachedObjectRef.current;
         }, [realm, originalObject, primaryKey]);
@@ -665,26 +667,6 @@ function createRealmProvider(realmConfig, RealmContext) {
                 realmRef.current = realm;
             }
         }, [realm]);
-        useEffect(() => {
-            const realmRef = currentRealm.current;
-            // Check if we currently have an open Realm. If we do not (i.e. it is the first
-            // render, or the Realm has been closed due to a config change), then we
-            // need to open a new Realm.
-            const shouldInitRealm = realmRef === null;
-            const initRealm = async () => {
-                const openRealm = await Realm.open(configuration.current);
-                setRealm(openRealm);
-            };
-            if (shouldInitRealm) {
-                initRealm().catch(console.error);
-            }
-            return () => {
-                if (realm) {
-                    realm.close();
-                    setRealm(null);
-                }
-            };
-        }, [configVersion, realm, setRealm]);
         if (!realm) {
             if (typeof Fallback === "function") {
                 return React.createElement(Fallback, null);

And I have a function like this to get the instance of the realm:

let realm: Realm | null = null;

export const usingRealmSingleton = () => {
  if (!realm || realm.isClosed) {
    realm = new Realm(realmConfig);
  }

  return realm;
};

@Acetyld
Copy link

Acetyld commented Aug 11, 2023

@Acetyld this is exactly what happens when we close a Realm, it closes all the instances of the same Realm. What I finally did was to have two instances, the first is the one that RealmProvider creates, and the other one is like a singleton instance for the rest of the code outside of the React tree, I think it is okay to just have that instance (the second one) open for the entire app lifecycle. Just make sure the RealmProvider will not close it. For example if there is a background worker running while the app is open and we close it, RealmProvider closes the instance automatically and the instance in our Worker will throw an error if we use it.

By now, I get it working using patch-package. This is my patch (by the way, it solves this issue and another one I discussed in #6021): patches/@realm+react+0.5.1.patch

diff --git a/node_modules/@realm/react/dist/index.js b/node_modules/@realm/react/dist/index.js
index 11274f5..558c916 100644
--- a/node_modules/@realm/react/dist/index.js
+++ b/node_modules/@realm/react/dist/index.js
@@ -356,6 +356,8 @@ function createUseObject(useRealm) {
                     updatedRef,
                 });
                 originalObjectRef.current = originalObject;
+                primaryKeyRef.current = primaryKey;
+                updatedRef.current = true;
             }
             return cachedObjectRef.current;
         }, [realm, originalObject, primaryKey]);
@@ -665,26 +667,6 @@ function createRealmProvider(realmConfig, RealmContext) {
                 realmRef.current = realm;
             }
         }, [realm]);
-        useEffect(() => {
-            const realmRef = currentRealm.current;
-            // Check if we currently have an open Realm. If we do not (i.e. it is the first
-            // render, or the Realm has been closed due to a config change), then we
-            // need to open a new Realm.
-            const shouldInitRealm = realmRef === null;
-            const initRealm = async () => {
-                const openRealm = await Realm.open(configuration.current);
-                setRealm(openRealm);
-            };
-            if (shouldInitRealm) {
-                initRealm().catch(console.error);
-            }
-            return () => {
-                if (realm) {
-                    realm.close();
-                    setRealm(null);
-                }
-            };
-        }, [configVersion, realm, setRealm]);
         if (!realm) {
             if (typeof Fallback === "function") {
                 return React.createElement(Fallback, null);

And I have a function like this to get the instance of the realm:

let realm: Realm | null = null;

export const usingRealmSingleton = () => {
  if (!realm || realm.isClosed) {
    realm = new Realm(realmConfig);
  }

  return realm;
};

Aah i see, and how are u using usingRealmSingleton in the rest of ur app, is this outside react components used? Or is it used as replacement for useRealm()

And yhea i dunno i dont want to keep realm open, for example we have a sync function, we open realm, and close at end. But this causes a whole different problem, bcs realm.write is sync and not async lets say we want to insert 1000 records, it happens sync, so my function continues and closes realm causing the insert to throw a error.

Maybe i am thinking the wrong way but we are facing mayor issues in current app, right now we are setting a canceled = true and inside the loop in realm.write we stop the loop, and when u set canceled to true we wait 5 seconds to make sure anything outgoing in realm is cancled so we dont get a "trying to access realm.."

For now our solution is in foreground jobs to just pass the realm from useRealm to the function (redux dispatch), but later on we are also gonna build a backend worker, my idea was to use the Expo TaskManager
for that.

@djgamarra
Copy link
Author

@Acetyld this is exactly what happens when we close a Realm, it closes all the instances of the same Realm. What I finally did was to have two instances, the first is the one that RealmProvider creates, and the other one is like a singleton instance for the rest of the code outside of the React tree, I think it is okay to just have that instance (the second one) open for the entire app lifecycle. Just make sure the RealmProvider will not close it. For example if there is a background worker running while the app is open and we close it, RealmProvider closes the instance automatically and the instance in our Worker will throw an error if we use it.
By now, I get it working using patch-package. This is my patch (by the way, it solves this issue and another one I discussed in #6021): patches/@realm+react+0.5.1.patch

diff --git a/node_modules/@realm/react/dist/index.js b/node_modules/@realm/react/dist/index.js
index 11274f5..558c916 100644
--- a/node_modules/@realm/react/dist/index.js
+++ b/node_modules/@realm/react/dist/index.js
@@ -356,6 +356,8 @@ function createUseObject(useRealm) {
                     updatedRef,
                 });
                 originalObjectRef.current = originalObject;
+                primaryKeyRef.current = primaryKey;
+                updatedRef.current = true;
             }
             return cachedObjectRef.current;
         }, [realm, originalObject, primaryKey]);
@@ -665,26 +667,6 @@ function createRealmProvider(realmConfig, RealmContext) {
                 realmRef.current = realm;
             }
         }, [realm]);
-        useEffect(() => {
-            const realmRef = currentRealm.current;
-            // Check if we currently have an open Realm. If we do not (i.e. it is the first
-            // render, or the Realm has been closed due to a config change), then we
-            // need to open a new Realm.
-            const shouldInitRealm = realmRef === null;
-            const initRealm = async () => {
-                const openRealm = await Realm.open(configuration.current);
-                setRealm(openRealm);
-            };
-            if (shouldInitRealm) {
-                initRealm().catch(console.error);
-            }
-            return () => {
-                if (realm) {
-                    realm.close();
-                    setRealm(null);
-                }
-            };
-        }, [configVersion, realm, setRealm]);
         if (!realm) {
             if (typeof Fallback === "function") {
                 return React.createElement(Fallback, null);

And I have a function like this to get the instance of the realm:

let realm: Realm | null = null;

export const usingRealmSingleton = () => {
  if (!realm || realm.isClosed) {
    realm = new Realm(realmConfig);
  }

  return realm;
};

Aah i see, and how are u using usingRealmSingleton in the rest of ur app, is this outside react components used? Or is it used as replacement for useRealm()

And yhea i dunno i dont want to keep realm open, for example we have a sync function, we open realm, and close at end. But this causes a whole different problem, bcs realm.write is sync and not async lets say we want to insert 1000 records, it happens sync, so my function continues and closes realm causing the insert to throw a error.

Maybe i am thinking the wrong way but we are facing mayor issues in current app, right now we are setting a canceled = true and inside the loop in realm.write we stop the loop, and when u set canceled to true we wait 5 seconds to make sure anything outgoing in realm is cancled so we dont get a "trying to access realm.."

For now our solution is in foreground jobs to just pass the realm from useRealm to the function (redux dispatch), but later on we are also gonna build a backend worker, my idea was to use the Expo TaskManager for that.

First, I'm using the useRealmSingleton() in Headless Tasks that do not have acces to the useRealm hook, just to get the instance of the Realm, but we are not closing that instance. The code that has access to the hook, just gets the instance in a param.

Second, the problem you are facing with Realm transactions is something like this?:

const realm = new Realm(config);
realm.write(() => {
  getData().then(data => {
    data.forEach((row) => {
      realm.create('Model', row);
    });
  });
});
realm.close();

@Acetyld
Copy link

Acetyld commented Aug 23, 2023

Uhm, i tested it and looks like the issue is still present

 <RealmProvider {...realmConfig} closeOnUnmount={false}>

We do above in our app globally, we use this for reactive reading etc..

In our sync function we do

const realm = await Realm.open(realmConfig);

realm.write(() => {
   realm.create(
       DepartmentName,
       Department.add(realm, item),
       UpdateMode.Modified,
   );
})
!realm?.isClosed && realm.close();

But the monent we call realm.close() it also closes the one in realmprovider, its so unique and weird, i dont get it.
I hoped the closeOnUmount would solve it.

@Acetyld
Copy link

Acetyld commented Sep 10, 2023

Any updates on above?

@takameyer
Copy link
Contributor

@Acetyld If you close realm outside of the RealmProvider, then it will also close the Realm in the provider. If you don't want it to be closed there, then don't close it here.
The new flag makes sure that the RealmProvider doesn't automatically close the opened Realm on unmount. Basically this puts everything in your control when it comes to closing the Realm.

@Acetyld
Copy link

Acetyld commented Sep 11, 2023

@djgamarra and me if i say correct had a different issue that is highly related to this one.
The main problem is we use a background worker that opens a new realm with same config, after doing background work we close it. But this causes the realmprovider to be also closed, and other way around as well (thats why the keepRealmOpen is so nice)

Is there a possiblty to look into this? @takameyer

@takameyer
Copy link
Contributor

@Acetyld How about not closing it in the background worker?

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants