Browse Source

Fix infinite recursion caused by inadvertent chunk loading/unloading Fixes #5112

nossr50 6 months ago
parent
commit
79ad86ff29

+ 3 - 0
Changelog.txt

@@ -1,3 +1,6 @@
+Version 2.2.028
+    Fix stack overflow during ChunkUnloadEvent
+
 Version 2.2.027
 Version 2.2.027
     Added Tridents / Crossbows to salvage.vanilla.yml config (see notes)
     Added Tridents / Crossbows to salvage.vanilla.yml config (see notes)
     Fixed an issue where Folia could have all of its threads lock up effectively killing the server
     Fixed an issue where Folia could have all of its threads lock up effectively killing the server

+ 1 - 1
pom.xml

@@ -2,7 +2,7 @@
     <modelVersion>4.0.0</modelVersion>
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.gmail.nossr50.mcMMO</groupId>
     <groupId>com.gmail.nossr50.mcMMO</groupId>
     <artifactId>mcMMO</artifactId>
     <artifactId>mcMMO</artifactId>
-    <version>2.2.027</version>
+    <version>2.2.028-SNAPSHOT</version>
     <name>mcMMO</name>
     <name>mcMMO</name>
     <url>https://github.com/mcMMO-Dev/mcMMO</url>
     <url>https://github.com/mcMMO-Dev/mcMMO</url>
     <scm>
     <scm>

+ 7 - 5
src/main/java/com/gmail/nossr50/listeners/ChunkListener.java

@@ -1,21 +1,23 @@
 package com.gmail.nossr50.listeners;
 package com.gmail.nossr50.listeners;
 
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
+import org.bukkit.Chunk;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.Listener;
 import org.bukkit.event.world.ChunkUnloadEvent;
 import org.bukkit.event.world.ChunkUnloadEvent;
 
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.List;
 
 
 public class ChunkListener implements Listener {
 public class ChunkListener implements Listener {
 
 
     @EventHandler(ignoreCancelled = true)
     @EventHandler(ignoreCancelled = true)
     public void onChunkUnload(ChunkUnloadEvent event) {
     public void onChunkUnload(ChunkUnloadEvent event) {
-        List<LivingEntity> matchingEntities
-                = mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk());
-        for(LivingEntity livingEntity : matchingEntities) {
-            mcMMO.getTransientEntityTracker().killSummonAndCleanMobFlags(livingEntity, null, false);
-        }
+        final Chunk unloadingChunk = event.getChunk();
+        Arrays.stream(unloadingChunk.getEntities())
+                .filter(entity -> entity instanceof LivingEntity)
+                .map(entity -> (LivingEntity) entity)
+                .forEach(livingEntity -> mcMMO.getTransientEntityTracker().removeTrackedEntity(livingEntity));
     }
     }
 }
 }

+ 31 - 16
src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.skills.taming.TrackedTamingEntity;
 import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.text.StringUtils;
 import com.gmail.nossr50.util.text.StringUtils;
-import org.bukkit.Chunk;
 import org.bukkit.Location;
 import org.bukkit.Location;
 import org.bukkit.Sound;
 import org.bukkit.Sound;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.LivingEntity;
@@ -19,10 +18,13 @@ import java.util.concurrent.ConcurrentHashMap;
 import static java.util.stream.Collectors.toSet;
 import static java.util.stream.Collectors.toSet;
 
 
 public class TransientEntityTracker {
 public class TransientEntityTracker {
-    private final @NotNull Map<UUID, Set<TrackedTamingEntity>> playerSummonedEntityTracker;
+    final @NotNull Map<UUID, Set<TrackedTamingEntity>> playerSummonedEntityTracker;
+    // used for fast lookups during chunk unload events
+    final @NotNull Set<LivingEntity> entityLookupCache;
 
 
     public TransientEntityTracker() {
     public TransientEntityTracker() {
-        playerSummonedEntityTracker = new ConcurrentHashMap<>();
+        this.playerSummonedEntityTracker = new ConcurrentHashMap<>();
+        this.entityLookupCache = ConcurrentHashMap.newKeySet();
     }
     }
 
 
     public void initPlayer(@NotNull Player player) {
     public void initPlayer(@NotNull Player player) {
@@ -33,14 +35,6 @@ public class TransientEntityTracker {
         cleanPlayer(player, player.getUniqueId());
         cleanPlayer(player, player.getUniqueId());
     }
     }
 
 
-    public @NotNull List<LivingEntity> getAllTransientEntitiesInChunk(@NotNull Chunk chunk) {
-        return playerSummonedEntityTracker.values().stream()
-                .flatMap(Collection::stream)
-                .map(TrackedTamingEntity::getLivingEntity)
-                .filter(livingEntity -> livingEntity.getLocation().getChunk() == chunk)
-                .toList();
-    }
-
     public int summonCountForPlayerOfType(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
     public int summonCountForPlayerOfType(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
         return getTrackedEntities(playerUUID, callOfTheWildType).size();
         return getTrackedEntities(playerUUID, callOfTheWildType).size();
     }
     }
@@ -48,6 +42,7 @@ public class TransientEntityTracker {
     public void addSummon(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
     public void addSummon(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
         playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
         playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
                 .add(trackedTamingEntity);
                 .add(trackedTamingEntity);
+        entityLookupCache.add(trackedTamingEntity.getLivingEntity());
     }
     }
 
 
     public void killSummonAndCleanMobFlags(@NotNull LivingEntity livingEntity, @Nullable Player player,
     public void killSummonAndCleanMobFlags(@NotNull LivingEntity livingEntity, @Nullable Player player,
@@ -77,9 +72,7 @@ public class TransientEntityTracker {
     }
     }
 
 
     public boolean isTransient(@NotNull LivingEntity livingEntity) {
     public boolean isTransient(@NotNull LivingEntity livingEntity) {
-        return playerSummonedEntityTracker.values().stream().anyMatch(
-                trackedEntities -> trackedEntities.stream()
-                        .anyMatch(trackedTamingEntity -> trackedTamingEntity.getLivingEntity().equals(livingEntity)));
+        return entityLookupCache.contains(livingEntity);
     }
     }
 
 
     private @NotNull Set<TrackedTamingEntity> getTrackedEntities(@NotNull UUID playerUUID,
     private @NotNull Set<TrackedTamingEntity> getTrackedEntities(@NotNull UUID playerUUID,
@@ -117,7 +110,29 @@ public class TransientEntityTracker {
     }
     }
 
 
     public void removeSummonFromTracker(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
     public void removeSummonFromTracker(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
-        playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
-                .remove(trackedTamingEntity);
+        if (playerSummonedEntityTracker.containsKey(playerUUID)) {
+            playerSummonedEntityTracker.get(playerUUID).remove(trackedTamingEntity);
+            entityLookupCache.remove(trackedTamingEntity.getLivingEntity());
+        }
+    }
+
+    public void removeTrackedEntity(@NotNull LivingEntity livingEntity) {
+        // Fail fast if the entity isn't being tracked
+        if (!entityLookupCache.contains(livingEntity)) {
+            return;
+        }
+
+        final List<TrackedTamingEntity> matchingEntities = new ArrayList<>();
+
+        // Collect matching entities without copying each set
+        playerSummonedEntityTracker.values().forEach(trackedEntitiesPerPlayer ->
+                trackedEntitiesPerPlayer.stream()
+                        .filter(trackedTamingEntity -> trackedTamingEntity.getLivingEntity() == livingEntity)
+                        .forEach(matchingEntities::add)
+        );
+
+        // Iterate over the collected list to handle removal and cleanup
+        matchingEntities.forEach(TrackedTamingEntity::run);
     }
     }
+
 }
 }