Solved ConcurrentModificationException

Discussion in 'Plugin Development' started by curlyfries1999, Aug 31, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    curlyfries1999

    Hello. I am writting a simple plugin that fires kittens from a bow then explodes them into a firework (Adorable, right?). I am getting this exception:

    Stack Trace (open)

    Code:
    20:40:37 [SEVERE] java.util.ConcurrentModificationException
    20:40:37 [SEVERE]      at java.util.HashMap$HashIterator.nextEntry(Unknown Sour
    ce)
    20:40:37 [SEVERE]      at java.util.HashMap$KeyIterator.next(Unknown Source)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.EntityTracker.updatePlay
    ers(EntityTracker.java:149)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.MinecraftServer.t(Minecr
    aftServer.java:581)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.DedicatedServer.t(Dedica
    tedServer.java:226)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.MinecraftServer.s(Minecr
    aftServer.java:486)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.MinecraftServer.run(Mine
    craftServer.java:419)
    20:40:37 [SEVERE]      at net.minecraft.server.v1_6_R2.ThreadServerApplication.
    run(SourceFile:582)
    20:40:37 [SEVERE] Encountered an unexpected exception ConcurrentModificationExce
    ption
    java.util.ConcurrentModificationException
            at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
            at java.util.HashMap$KeyIterator.next(Unknown Source)
            at net.minecraft.server.v1_6_R2.EntityTracker.updatePlayers(EntityTracke
    r.java:149)
            at net.minecraft.server.v1_6_R2.MinecraftServer.t(MinecraftServer.java:5
    81)
            at net.minecraft.server.v1_6_R2.DedicatedServer.t(DedicatedServer.java:2
    26)
            at net.minecraft.server.v1_6_R2.MinecraftServer.s(MinecraftServer.java:4
    86)
            at net.minecraft.server.v1_6_R2.MinecraftServer.run(MinecraftServer.java
    :419)
            at net.minecraft.server.v1_6_R2.ThreadServerApplication.run(SourceFile:5
    82)


    but it does not tell me where I have gone wrong. This is my code:

    Main (open)

    Code:java
    1. import java.util.ArrayList;
    2. import java.util.logging.Level;
    3. import java.util.logging.Logger;
    4.  
    5. import org.bukkit.ChatColor;
    6. import org.bukkit.Material;
    7. import org.bukkit.command.Command;
    8. import org.bukkit.command.CommandSender;
    9. import org.bukkit.craftbukkit.v1_6_R2.entity.CraftEntity;
    10. import org.bukkit.entity.Player;
    11. import org.bukkit.inventory.ItemStack;
    12. import org.bukkit.inventory.meta.ItemMeta;
    13. import org.bukkit.plugin.java.JavaPlugin;
    14.  
    15. public class Main extends JavaPlugin {
    16.  
    17. static Logger logger = Logger.getLogger("Minecraft");
    18. static ArrayList<CraftEntity> cats = new ArrayList<CraftEntity>();
    19.  
    20. @Override
    21. public void onEnable() {
    22. getServer().getPluginManager().registerEvents(new BowListener(), this);
    23. getServer().getPluginManager().registerEvents(new PlayerListener(), this);
    24. }
    25.  
    26. @Override
    27. public void onDisable() {
    28. int catCount = 0;
    29. for (CraftEntity c : cats) {
    30. c.remove();
    31. catCount++;
    32. }
    33. logger.log(Level.INFO, "[Kitty Bow] Removed " + catCount + " fluffy kitties!");
    34. }
    35.  
    36. @Override
    37. public boolean onCommand(CommandSender sender, Command cmd, String CommandLabel, String[] args) {
    38. Player player = (Player) sender;
    39.  
    40. ItemStack kittyBow = new ItemStack(Material.BOW);
    41.  
    42. ArrayList<String> kbLore = new ArrayList<String>();
    43. kbLore.add(rainbow("Kitty Bow"));
    44. ItemMeta kbMeta = kittyBow.getItemMeta();
    45.  
    46. kbMeta.setLore(kbLore);
    47.  
    48. kittyBow.setItemMeta(kbMeta);
    49.  
    50. player.getInventory().addItem(kittyBow);
    51. return false;
    52. }
    53.  
    54. private String rainbow(String string) {
    55. String rainbowised = "";
    56. ChatColor[] colours = {ChatColor.RED, ChatColor.GOLD, ChatColor.YELLOW, ChatColor.GREEN, ChatColor.BLUE, ChatColor.DARK_BLUE, ChatColor.LIGHT_PURPLE};
    57. for (int i = 0; i < string.length(); i++) {
    58. rainbowised = rainbowised + colours[listNum(i, colours.length)] + string.charAt(i);
    59. }
    60. return rainbowised;
    61. }
    62.  
    63. private int listNum(int get, int size) {
    64. int fin = get;
    65. while (fin >= size) {
    66. fin -= size;
    67. }
    68. return fin;
    69. }
    70.  
    71. }
    72.  


    BowListener (open)

    Code:java
    1. import net.minecraft.server.v1_6_R2.EntityOcelot;
    2. import org.bukkit.ChatColor;
    3. import org.bukkit.Color;
    4. import org.bukkit.FireworkEffect;
    5. import org.bukkit.FireworkEffect.Type;
    6. import org.bukkit.Location;
    7. import org.bukkit.craftbukkit.v1_6_R2.entity.CraftEntity;
    8. import org.bukkit.entity.Arrow;
    9. import org.bukkit.entity.EntityType;
    10. import org.bukkit.entity.LivingEntity;
    11. import org.bukkit.entity.Player;
    12. import org.bukkit.event.EventHandler;
    13. import org.bukkit.event.Listener;
    14. import org.bukkit.event.entity.ProjectileLaunchEvent;
    15. import org.bukkit.potion.PotionEffect;
    16. import org.bukkit.potion.PotionEffectType;
    17.  
    18. public class BowListener implements Listener {
    19.  
    20. int time = 5;
    21.  
    22. @EventHandler
    23. public void onLaunch(ProjectileLaunchEvent event) {
    24. if (event.getEntity() instanceof Arrow) {
    25. Arrow a = (Arrow) event.getEntity();
    26. if (a.getShooter() instanceof Player) {
    27. final Player p = (Player) a.getShooter();
    28. try {
    29. if (ChatColor.stripColor(p.getItemInHand().getItemMeta().getLore().get(0)).equals("Kitty Bow")) {
    30. event.setCancelled(true);
    31.  
    32. final CraftEntity craftCat = (CraftEntity) p.getWorld().spawnEntity(p.getLocation().add(0, 2, 0), EntityType.OCELOT);
    33. final EntityOcelot cat = (EntityOcelot) craftCat.getHandle();
    34. cat.setLocation(cat.locX, cat.locY, cat.locZ, 45, 45);
    35. cat.setOwnerName(p.getName());
    36. cat.setTamed(true);
    37. cat.setSitting(true);
    38. cat.setCatType(random(0, 2));
    39. craftCat.setVelocity(a.getVelocity());
    40.  
    41. ((LivingEntity)craftCat).addPotionEffect(new PotionEffect(PotionEffectType.SLOW, time * 20, 30));
    42.  
    43. Main.cats.add(craftCat);
    44.  
    45. Thread t = new Thread(new Runnable() {
    46. @Override
    47. public void run() {
    48. try {
    49. Thread.sleep(time * 1000);
    50. } catch (InterruptedException IE) {
    51. }
    52. int ranType = random(0, 1);
    53. Type type = null;
    54.  
    55. if (ranType == 0) {
    56. type = Type.BALL;
    57. } else if (ranType == 1) {
    58. type = Type.BURST;
    59. }
    60.  
    61. FireworkEffect effect = FireworkEffect.builder().flicker(false).withColor(Color.fromRGB(random(0, 255), random(0, 255), random(0, 255))).withFade(Color.fromRGB(random(0, 255), random(0, 255), random(0, 255))).with(type).trail(false).build();
    62.  
    63. try {
    64. InstantFirework.playFirework(p.getWorld(), new Location(p.getWorld(), cat.locX, cat.locY, cat.locZ), effect);
    65. } catch (Exception e) {
    66. e.printStackTrace();
    67. }
    68.  
    69. Main.cats.remove(craftCat);
    70. craftCat.remove();
    71. }
    72. });
    73. t.start();
    74. }
    75. } catch (NullPointerException npe) {
    76. }
    77. }
    78. }
    79. }
    80.  
    81. private int random(int Min, int Max) {
    82. return Min + (int)(Math.random() * ((Max - Min) + 1));
    83. }
    84.  
    85. }


    PlayerListener (open)

    Code:java
    1. import java.util.logging.Level;
    2.  
    3. import org.bukkit.craftbukkit.v1_6_R2.entity.CraftEntity;
    4. import org.bukkit.entity.Tameable;
    5. import org.bukkit.event.EventHandler;
    6. import org.bukkit.event.Listener;
    7. import org.bukkit.event.player.PlayerQuitEvent;
    8.  
    9. public class PlayerListener implements Listener {
    10.  
    11. @EventHandler
    12. public void onPlayerLeave(PlayerQuitEvent e) {
    13. int catCount = 0;
    14. for (CraftEntity c : Main.cats) {
    15. if (((Tameable) c).getOwner().equals(e.getPlayer())) {
    16. c.remove();
    17. catCount++;
    18. }
    19. }
    20. if (catCount != 0) {
    21. Main.logger.log(Level.INFO, "Removed " + (catCount == 1 ? e.getPlayer().getName() + "'s cat" : catCount + " of " + e.getPlayer().getName() + "'s cats"));
    22. }
    23. }
    24.  
    25. }
    26.  


    InstantFirework (open)

    Code:java
    1. import java.lang.reflect.Method;
    2.  
    3. import org.bukkit.FireworkEffect;
    4. import org.bukkit.entity.Firework;
    5. import org.bukkit.inventory.meta.FireworkMeta;
    6. import org.bukkit.Location;
    7. import org.bukkit.World;
    8.  
    9. public class InstantFirework {
    10.  
    11. private static Method world_getHandle = null;
    12. private static Method nms_world_broadcastEntityEffect = null;
    13. private static Method firework_getHandle = null;
    14.  
    15. public static void playFirework(World world, Location loc, FireworkEffect fe) throws Exception {
    16. Firework fw = (Firework) world.spawn(loc, Firework.class);
    17. Object nms_world = null;
    18. Object nms_firework = null;
    19.  
    20. if(world_getHandle == null) {
    21. world_getHandle = getMethod(world.getClass(), "getHandle");
    22. firework_getHandle = getMethod(fw.getClass(), "getHandle");
    23. }
    24. nms_world = world_getHandle.invoke(world, (Object[]) null);
    25. nms_firework = firework_getHandle.invoke(fw, (Object[]) null);
    26. if(nms_world_broadcastEntityEffect == null) {
    27. nms_world_broadcastEntityEffect = getMethod(nms_world.getClass(), "broadcastEntityEffect");
    28. }
    29. FireworkMeta data = (FireworkMeta) fw.getFireworkMeta();
    30. data.clearEffects();
    31. data.setPower(1);
    32. data.addEffect(fe);
    33. fw.setFireworkMeta(data);
    34.  
    35. nms_world_broadcastEntityEffect.invoke(nms_world, new Object[] {nms_firework, (byte) 17});
    36. fw.remove();
    37. }
    38.  
    39. private static Method getMethod(Class<?> cl, String method) {
    40. for(Method m : cl.getMethods()) {
    41. if(m.getName().equals(method)) {
    42. return m;
    43. }
    44. }
    45. return null;
    46. }
    47.  
    48. }
    49.  



    Any help will be appriciated!
    Thanks,
    -Curly
     
  2. Offline

    Comphenix

    That's because you're calling Bukkit API/NMS in asynchronous threads (line 45 in BowListener), just to delay execution of code. This is never a good idea, it can potentially cause the server to crash entirely, or even worse - overwrite the world files with corrupt data. Getting a ConcurrentModificationException is arguably the best possible outcome here, since at least you know you did something wrong before servers are randomly deadlocked.

    So - schedule synchronous tasks instead. That way, the code will be executed after a delay like you want, but on the main server thread where you can call the Bukkit API to your hearts content.
     
    Hoolean likes this.
  3. Offline

    CubieX

    Making BukkitAPI calls in an async thread is a big no no.
    Use a Bukit Scheduler and like Comphenix said, use a synchronous task for this.
    A delayed task like "Bukkit.getServer().getScheduler().runTaskLater()" should work for this.

    But your exception is caused by this and similar code parts:
    Code:
     for (CraftEntity c : Main.cats) {
    if (((Tameable) c).getOwner().equals(e.getPlayer())) {
    c.remove();
    catCount++;
    }
    }
    You are deleting entries from an ArrayList while looping through it. (in more than one occurrence)
    You should use an Interator for this.
     
  4. Offline

    Comphenix

    Well, the remove() method here simply marks the entity as dead, which will be handled during the next tick. It doesn't remove anything from any array list, though that is exactly what OP should do in onDisable(), to avoid any potential memory leaks.

    If it was one of those mistakes, the exception would have occurred in the plugin itself, not in some arbitrary part of Minecraft. For that to occur, the plugin must have executed code concurrently with the main thread, which means abuse of threading.
     
  5. Offline

    xTrollxDudex

    curlyfries1999
    A lotta things could go wrong in that new thread you started...
    1. Print stack trace instead of doing nothing in the try catch
    2. Why are you catching an NPE??
    3. Bukkit methods should only be called from Sync tasks as overly emphasized
    4. No need for a different thread
     
  6. Offline

    curlyfries1999

    So, I need to replace the thread with a scheduler?
     
  7. Code:java
    1. for (CraftEntity c : Main.cats.clone()) {
    2. if (((Tameable) c).getOwner().equals(e.getPlayer())) {
    3. Main.cats.remove(c);
    4. //c.remove();
    5. catCount++;
    6. }
    7. }



    Use Main.cats.clone() to iterate over the the cloned Hash-map, because you can't delete an entity in hash map while iterating through it.
     
  8. Offline

    frymaster

    curlyfries1999 likes this.
  9. Offline

    curlyfries1999

    frymaster Thank you very much! That worked great! :D
     
Thread Status:
Not open for further replies.

Share This Page