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

This commit is contained in:
nossr50 2024-11-16 14:33:51 -08:00
parent 4cb3d3181b
commit 79ad86ff29
4 changed files with 42 additions and 22 deletions

View File

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

View File

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

View File

@ -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 final Chunk unloadingChunk = event.getChunk();
= mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk()); Arrays.stream(unloadingChunk.getEntities())
for(LivingEntity livingEntity : matchingEntities) { .filter(entity -> entity instanceof LivingEntity)
mcMMO.getTransientEntityTracker().killSummonAndCleanMobFlags(livingEntity, null, false); .map(entity -> (LivingEntity) entity)
} .forEach(livingEntity -> mcMMO.getTransientEntityTracker().removeTrackedEntity(livingEntity));
} }
} }

View File

@ -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( return entityLookupCache.contains(livingEntity);
trackedEntities -> trackedEntities.stream()
.anyMatch(trackedTamingEntity -> trackedTamingEntity.getLivingEntity().equals(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()) if (playerSummonedEntityTracker.containsKey(playerUUID)) {
.remove(trackedTamingEntity); 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);
}
}