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

View File

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

View File

@ -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));
}
}

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.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);
}
}