Search code examples
javaiterationconcurrentmodification

A strange ConcurrentModificationException on iteration


this is probably the closest case to mine, but that didn't help either.

I'm getting the ConcurrentModificationException here:

for (Iterator<ProjectileBase> iterator = projectiles.iterator(); iterator.hasNext();) {
        ProjectileBase proj = iterator.next();  < here
        if (proj == null || !proj.isValid()) {
            iterator.remove();
            continue;
        }
        if (((!proj.ignoreRange()) ? proj.lived <= proj.data.getRange() : true)){
            proj.lived++;
            proj.onTick();
        } else {
            MissileHitEvent event = new MissileHitEvent(proj.shooter, proj.wand, proj, false, null);
            Bukkit.getPluginManager().callEvent(event);
            if (!event.isCancelled()) {
                iterator.remove();
                continue;
            } else {
                proj.lived = 0;
            }
        }
    }

Even though I did as suggested here ?

The list is specified like this:

private List<ProjectileBase> projectiles = new ArrayList<ProjectileBase>();

which is intialized on the class construction. What's the problem?

EDIT: console log:

[10:01:58] [Craft Scheduler Thread - 3754/WARN]: Exception in thread "Craft Scheduler Thread - 3754" 
[10:01:58] [Craft Scheduler Thread - 3754/WARN]: org.apache.commons.lang.UnhandledException: Plugin MagicWands v1.0 generated an exception while executing task 247
    at org.bukkit.craftbukkit.v1_9_R2.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:57)
    at com.destroystokyo.paper.ServerSchedulerReportingWrapper.run(ServerSchedulerReportingWrapper.java:22)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at io.jettymc.DataHolders.ProjectileManager.iterate(ProjectileManager.java:117)
    at io.jettymc.DataHolders.ProjectileManager$1.run(ProjectileManager.java:232)
    at org.bukkit.craftbukkit.v1_9_R2.scheduler.CraftTask.run(CraftTask.java:58)
    at org.bukkit.craftbukkit.v1_9_R2.scheduler.CraftAsyncTask.run(CraftAsyncTask.java:53)
    ... 4 more

EDIT 2: Well, I guess it's worth telling, I'm building this project on Bukkit/Spigot (Minecraft server & API).. but I doubt it's the cause of this error?


Solution

  • To help narrow down the problem, there is a trick that might help.

    Assuming projectiles is the only reference to the ArrayList, you can temporarily replace it with an immutable list, while you're iterating, so only the Iterator can modify the list. If some other code tries to modify the list, an exception will occur, which should let you know where it happens, assuming your error handling is not messed up.

    Example:

    List<ProjectileBase> projectilesHold = projectiles;
    projectiles = Collections.unmodifiableList(projectiles);
    try {
        for (Iterator<ProjectileBase> iterator = projectilesHold.iterator(); iterator.hasNext();) {
            // other code here is unchanged
        }
    } finally {
        projectiles = projectilesHold;
    }
    

    The code saves the modifiable list in the "hold" variable, then wraps the list to make it unmodifiable. The finally block will ensure that the modifiable list is restored, no matter what.

    The for loop iterator was then modified to use the modifiable list in "hold", so its remove() method works, but anywhere else the projectiles field is now unmodifiable for the duration of the iteration.

    This is for debug only. Once you identify the problem and correct it, remove the logic again.