realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.47k stars 1.75k forks source link

Object no longer managed by realm (realm 1.0.0) #3057

Closed Flydiverny closed 8 years ago

Flydiverny commented 8 years ago

Goal

Clearing temporary data when signing out of application :)

Expected Results

Data is removed when we remove it without crashing!

Actual Results

java.lang.IllegalStateException: Object is no longer managed by Realm. Has it been deleted?
                 at io.realm.internal.InvalidRow.getStubException(InvalidRow.java:192)
                 at io.realm.internal.InvalidRow.getLinkList(InvalidRow.java:118)
                 at io.realm.LockInfoRealmProxy.realmGet$persons(LockInfoRealmProxy.java:349)
...

Steps & Code to Reproduce

public class DataCleaner {

    private final Realm mRealm;
    private final Date mDateLimit;

    public DataCleaner(Realm realm, Date dateLimit) {
        mRealm = realm;
        mDateLimit = dateLimit;
    }

    public void performCleanup() {
        mRealm.beginTransaction();

        removeStuff(); // 5 methods removing different classes that might have references to persons

        // Needs to be done after stuff
        removePersons();

        // Needs to be done after persons since we keep locks linked to a person.
        removeLocks();

        removeFlexibleData(); // Dump some tables

        mRealm.commitTransaction();

    }

    private void removeLocks() {
        // Remove unused locks
        RealmResults<LockInfo> locksToRemove = mRealm.where(LockInfo.class).findAll();

        while (locksToRemove.size() > 0) {
            LockInfo lock = locksToRemove.get(0);

            if (lock.getPersons().size() == 0) {

                if (lock.getTBDN() != null) {
                    lock.getTBDN().deleteFromRealm(); // Removing lock -> remove TBDN
                }
            }

            lock.deleteFromRealm();
        }
    }

    private <T extends RealmObject> boolean hasPerson(Class<T> realmObj, Person p) {
        return mRealm.where(realmObj).equalTo("person.ID", p.getID()).count() != 0;
    }

    private void removePersons() {
        // Delete all persons not needed for a other classes
        RealmQuery<Person> query = mRealm.where(Person.class);
        for (Person p : mRealm.where(Person.class).findAll()) {
            // Do not remove person which is needed by other classes
            if (mRealm.where(WWW.class).equalTo("Persons.ID", p.getID()).count() != 0
                    || hasPerson(X.class, p)
                    || hasPerson(Y.class, p)
                    || hasPerson(Z.class, p)
                    || hasPerson(XX.class, p)) {
                query.not().equalTo("ID", p.getID());
            }
        }

        RealmResults<Person> personsToRemove = query.findAll();
        for (int i = 0; i < personsToRemove.size(); i++) {
            personsToRemove.get(i).getA().deleteAllFromRealm();
            personsToRemove.get(i).getB().deleteAllFromRealm();
            personsToRemove.get(i).getC().deleteAllFromRealm();
        }

        // Remove persons after iteration to make sure we iterate over all of them (list will change on removal from it)
        personsToRemove.deleteAllFromRealm();
    }

    private void removeFlexibleData() {

        mRealm.delete(A.class);

        mRealm.delete(B.class);

        mRealm.delete(C.class);
        mRealm.delete(D.class);
        mRealm.delete(E.class);
        mRealm.delete(F.class);
        mRealm.delete(G.class);
        mRealm.delete(H.class);
        mRealm.delete(I.class);
        mRealm.delete(J.class);
        mRealm.delete(K.class);
        mRealm.delete(L.class);
        mRealm.delete(M.class);

    }
}

So we tried to split it up in several transactions & commits with no luck. This worked previously for us in realm 0.88.3, we have not done any extensive testing on versions 0.89.0 thru 0.91.0 more than basically it compiled.

Version of Realm and tooling

Realm version(s): 1.0.1 Android Studio version: 2.1.2

Flydiverny commented 8 years ago

This crash also occurs in realm 0.90.0 and in 0.89.1, I'm thinking it has to do with the changes to the event loop/looper refresh changes.

cmelchior commented 8 years ago

It has. This pattern here no longer works:

        while (locksToRemove.size() > 0) {
            LockInfo lock = locksToRemove.get(0);

            if (lock.getPersons().size() == 0) {

                if (lock.getTBDN() != null) {
                    lock.getTBDN().deleteFromRealm(); // Removing lock -> remove TBDN
                }
            }

            lock.deleteFromRealm();
        }

Instead do a normal loop like so:

        for (LockInfo lock : locksToRemove) {
            if (lock.getPersons().size() == 0) {

                if (lock.getTBDN() != null) {
                    lock.getTBDN().deleteFromRealm(); // Removing lock -> remove TBDN
                }
            }

            lock.deleteFromRealm();
        }
Zhuinden commented 8 years ago

Technically it's the change in 0.89.0 where RealmResults were changed to no longer live-update when a condition for an element is no longer met, they just become invalid with !isValid().

So I personally would recommend iteration from the end, that works in both versions, old and new:

for(int i = results.size() - 1; i >= 0; i--) {
    SomeObj someObj = results.get(i);
    //blah blah
    if(/*blah*/) {
        someObj.deleteFromRealm();
    }
}
Flydiverny commented 8 years ago

@cmelchior Hi!

Tested changing this seems to indeed resolve the issue :)

Thanks.