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

ConcurrentModificationException when using iterators methods #640

Closed
sergeyfitis opened this issue Dec 4, 2014 · 40 comments

Comments

Projects
None yet
@sergeyfitis
Copy link

commented Dec 4, 2014

Hi, I have such exception even I using iterators methods:
Realm version 0.75.1, Android studio 1.0rc2

Caused by: java.util.ConcurrentModificationException: No outside changes to a Realm is allowed while iterating a RealmResults. Use iterators methods instead.
            at io.realm.RealmResults.assertRealmIsStable(RealmResults.java:405)
            at io.realm.RealmResults.access$200(RealmResults.java:44)
            at io.realm.RealmResults$RealmResultsIterator.hasNext(RealmResults.java:422)
            at com.coachesdirectory.coach.db.RealmHelper$5.doInBackground(RealmHelper.java:360)
            at com.coachesdirectory.coach.db.RealmHelper$5.doInBackground(RealmHelper.java:347)
            at android.os.AsyncTask$2.call(AsyncTask.java:288)
            at java.util.concurrent.FutureTask.run(FutureTask.java:237)
            at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:818)

Here is part of my code:

****AsyncTask*****
List<SchoolResult> favoriteSchools = params[0];
Realm realm = Realm.getInstance(context);
realm.beginTransaction();

// Set all schools to remove
RealmResults<RealmSchoolResult> markToRemove = realm.where(RealmSchoolResult.class)
                        .equalTo("userAccountId", userId)
                        .findAll();
Iterator<RealmSchoolResult> schoolResultIterator = markToRemove.iterator();
while (schoolResultIterator.hasNext()) { // Crash at this line
  RealmSchoolResult realmSchoolResult = schoolResultIterator.next();
  realmSchoolResult.setNeedRemove(true);
}

@sergeyfitis sergeyfitis changed the title ConcurrentModificationException ConcurrentModificationException when using iterators methods Dec 4, 2014

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2014

That exception is being thrown because another part of your code is modifying the RealmSchoolResult objects. A RealmResults is like a normal Array that way. You cannot remove or add elements that fit your query while you iterate the results of that query.

Do you perhaps have some other code in another thread that add those elements to the database?

@sergeyfitis

This comment has been minimized.

Copy link
Author

commented Dec 4, 2014

cmelchior, in another thread I only getting those elements from db, not modifying anything. But it executing before this thread(thread with error).

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2014

@sergeyfitis There shouldn't be a problem querying the data on another thread, but how do you get objects into Realm then?

In any case, I will need to see some more code to properly debug this. Are you in a position to share your source code or can you create a small sample project that can reproduce the error?

@sergeyfitis

This comment has been minimized.

Copy link
Author

commented Dec 4, 2014

@cmelchior I will try to create sample project.

@sergeyfitis

This comment has been minimized.

Copy link
Author

commented Dec 4, 2014

@cmelchior, I created sample project. It crashing with the same error.
https://gist.github.com/sergeyfitis/5df1632dad5c3a255cf0

User class:

public class User extends RealmObject {
    private String name;
    private boolean needRemove;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isNeedRemove() {
        return needRemove;
    }

    public void setNeedRemove(boolean needRemove) {
        this.needRemove = needRemove;
    }
}
@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Dec 4, 2014

Hi @sergeyfitis Thank you very much. I can reproduce it as well now. I will investigate.

@cmelchior cmelchior added the T:Bug label Dec 4, 2014

@sergeyfitis

This comment has been minimized.

Copy link
Author

commented Dec 4, 2014

Welcome :)

@demonar

This comment has been minimized.

Copy link

commented Dec 5, 2014

having the same issue.

@petersnoopy

This comment has been minimized.

Copy link

commented Dec 8, 2014

Is there a 'dirty' fix to avoid the issue until a new version will be released?

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Dec 8, 2014

@petersnoopy Yes, just use a normal for loop like this:

realm.beginTransaction();
for (int i = 0; i < results.size(); i++) {
  results.get(i).setProperty("foo");
}
realm.commitTransaction();
@petersnoopy

This comment has been minimized.

Copy link

commented Dec 8, 2014

@cmelchior Thanks!

@TepesLucian

This comment has been minimized.

Copy link

commented Dec 10, 2014

ETA on this one?

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Jan 19, 2015

Just a quick note that this has not been forgotten, but has been delayed by some needed changes to the core database. There is no timeframe, but we are actively working on it now.

@bmunkholm bmunkholm added the P1 label Jan 30, 2015

@Queatz

This comment has been minimized.

Copy link

commented Mar 16, 2015

Getting this as well.

@GokhanArik

This comment has been minimized.

Copy link

commented Mar 29, 2015

Any updates on this?

@crysan

This comment has been minimized.

Copy link

commented Apr 11, 2015

no ((

@cmelchior cmelchior added the Blocked label Apr 15, 2015

@ikarius

This comment has been minimized.

Copy link

commented May 6, 2015

(bump)

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented May 6, 2015

Sorry for the delay. The changes in the core database has taken a bit longer than anticipated. It is almost done at this point though, which means we can fix this bug soon.

@ikarius

This comment has been minimized.

Copy link

commented May 6, 2015

Thx !
Btw

for (int i = 0; i < results.size(); i++) {
results.get(i).setProperty("foo");
}

does not work either, because result seems to be updated dynamically (which is cool but unexpected IMHO)

@koalahamlet

This comment has been minimized.

Copy link

commented May 6, 2015

This issue would be great to see closed soon.

@shanelk

This comment has been minimized.

Copy link

commented May 19, 2015

The dirty fix that I have used is to create an array from the RealmResults.

MyModel[] modelArray = results.toArray(new MyModel[results.size()]);

for(MyModel model : modelArray) {
model.setProperty("foo");
}

@emanuelez

This comment has been minimized.

Copy link
Contributor

commented May 20, 2015

A workaround is to iterate the RealmResults backwards:

for (int i = results.size() -1; i >=0; i--) {
   results.get(i).setProperty("foo");
}
@dalinaum

This comment has been minimized.

Copy link
Contributor

commented Oct 10, 2015

I know avoid calling sync_if_needed is not possible to achieve using standard iterator patterns in Java.. We sould find other method to cover this use case.

So bug should be removed and enhancement should be added?

@Zhuinden

This comment has been minimized.

Copy link
Contributor

commented Feb 23, 2016

The RealmResults are updated immediately in case of a transaction (and you can only write in a transaction, so you're pretty much definitely in a transaction), and transactions (beginTransaction()) are blocking so you are definitely updating the instances you want to update.

As such, if I know that the modified value will remove the realm objects from the realm results, then I just do the following:

    RealmResults<Something> results = realm.where(Something.class).equalTo("property", "bar").findAll();
    realm.beginTransaction();
    for (int i = 0; i < results.size(); i++) {
          results.get(i--).setProperty("foo");
    }
    realm.commitTransaction();

EDIT (2016-04-26): This is no longer the case since 0.89.0

    for (int i = results.size()-1; i >= 0; i--) {
          results.get(i).setProperty("foo");
    }

This is the version-idempotent version.

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Mar 16, 2016

For reference. We are actively working on fixing this now in #2124
It means there will be some changes to how the live-update of RealmResults work as they will now be "frozen" for the duration of a Looper event, but will still be automatically updated for the next loop. For most common programming patterns this will not make a difference, but instead for...each and simple iterators will work as expected.

@Zhuinden

This comment has been minimized.

Copy link
Contributor

commented Mar 17, 2016

@cmelchior will transactions still directly affect the RealmResults<T> that you're iterating, essentially removing elements from the query? (Does the code snippet I posted above still work?) Or will that RealmResults<T> only be updated on the next looper event?

@cmelchior cmelchior added S:Review and removed S:In Progress labels Apr 15, 2016

@cmelchior cmelchior modified the milestones: 0.89, 1.0.0-rc1 Apr 19, 2016

@cmelchior cmelchior removed the S:Review label Apr 21, 2016

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Apr 21, 2016

Fixed in 0.89

@fobidlim

This comment has been minimized.

Copy link

commented Apr 22, 2016

+1

@meierjan

This comment has been minimized.

Copy link

commented Apr 26, 2016

By 0.89 you mean 0.89.1? Can one use a for-each loop again?

@Zhuinden

This comment has been minimized.

Copy link
Contributor

commented Apr 26, 2016

@meierjan yes.

@meierjan

This comment has been minimized.

Copy link

commented Apr 28, 2016

I am still seeing this in 0.89.1. Anything that can be dont wront to provoke this?

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Apr 28, 2016

You might still hit this depending on what you do. We just aimed to have the same semantics as an normal ArrayList (which also throws ConcurrentModificationException if you remove/add while iterating).

What does your code look like?

@cmelchior

This comment has been minimized.

Copy link
Contributor

commented Apr 29, 2016

The standard List.remove() method doesn't work for normal ArrayList iterators either .
You should use the Iterator.remove() method instead, it ensures that the iterator count isn't counting wrong https://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html#remove():

Iterator it = oldPlaceOfBike.getBikes().iterator();
while(it.hasNext()) {
  BikeEntity bike = it.next();
  if (bike.getUid().equals(bikeWithNewInformation.getUid())) {
        it.remove();
  }
}
@Isabellle

This comment has been minimized.

Copy link

commented Aug 11, 2016

@cmelchior Do you see anything wrong that I'm doing here? That would be great if you could just take a look. Thanks a lot. http://stackoverflow.com/questions/38892798/refresh-viewpager-from-within-the-drawerlayout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.