Skip to content

Commit

Permalink
Fix infinite recursion caused by inadvertent chunk loading/unloading F…
Browse files Browse the repository at this point in the history
…ixes #5112
  • Loading branch information
nossr50 committed Nov 16, 2024
1 parent 4cb3d31 commit 79ad86f
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 22 deletions.
3 changes: 3 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/com/gmail/nossr50/listeners/ChunkListener.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
47 changes: 31 additions & 16 deletions src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
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;
Expand All @@ -19,10 +18,13 @@
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) {
Expand All @@ -33,21 +35,14 @@ public void cleanupPlayer(@NotNull Player player) {
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();
}

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,
Expand Down Expand Up @@ -77,9 +72,7 @@ public void killSummonAndCleanMobFlags(@NotNull LivingEntity livingEntity, @Null
}

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,
Expand Down Expand Up @@ -117,7 +110,29 @@ public void killAndCleanSummon(@NotNull UUID playerUUID, @Nullable Player player
}

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

}

0 comments on commit 79ad86f

Please sign in to comment.