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
     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

+ 1 - 1
pom.xml

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

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

@@ -1,21 +1,23 @@
 package com.gmail.nossr50.listeners;
 
 import com.gmail.nossr50.mcMMO;
+import org.bukkit.Chunk;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.world.ChunkUnloadEvent;
 
+import java.util.Arrays;
 import java.util.List;
 
 public class ChunkListener implements Listener {
 
     @EventHandler(ignoreCancelled = true)
     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.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.text.StringUtils;
-import org.bukkit.Chunk;
 import org.bukkit.Location;
 import org.bukkit.Sound;
 import org.bukkit.entity.LivingEntity;
@@ -19,10 +18,13 @@ import java.util.concurrent.ConcurrentHashMap;
 import static java.util.stream.Collectors.toSet;
 
 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() {
-        playerSummonedEntityTracker = new ConcurrentHashMap<>();
+        this.playerSummonedEntityTracker = new ConcurrentHashMap<>();
+        this.entityLookupCache = ConcurrentHashMap.newKeySet();
     }
 
     public void initPlayer(@NotNull Player player) {
@@ -33,14 +35,6 @@ public class TransientEntityTracker {
         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) {
         return getTrackedEntities(playerUUID, callOfTheWildType).size();
     }
@@ -48,6 +42,7 @@ public class TransientEntityTracker {
     public void addSummon(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
         playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
                 .add(trackedTamingEntity);
+        entityLookupCache.add(trackedTamingEntity.getLivingEntity());
     }
 
     public void killSummonAndCleanMobFlags(@NotNull LivingEntity livingEntity, @Nullable Player player,
@@ -77,9 +72,7 @@ public class TransientEntityTracker {
     }
 
     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,
@@ -117,7 +110,29 @@ public class TransientEntityTracker {
     }
 
     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);
     }
+
 }