149 lines
8.8 KiB
Diff
149 lines
8.8 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Mon, 25 May 2020 11:02:42 -0400
|
|
Subject: [PATCH] Unload leaked Cached Chunks
|
|
|
|
Due to some complexity in mojangs complicated chain of juggling
|
|
whether or not a chunk should be unloaded when the last ticket is
|
|
removed, many chunks are remaining around in the cache.
|
|
|
|
These chunks are never being targetted for unload because they are
|
|
vastly out of view distance range and have no reason to be looked at.
|
|
|
|
This is a huge issue for performance because we have to iterate these
|
|
chunks EVERY TICK... This is what's been leading to high SELF time in
|
|
Ticking Chunks timings/profiler results.
|
|
|
|
We will now detect these chunks in that iteration, and automatically
|
|
add it to the unload queue when the chunk is found without any tickets.
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..1209fbf1c906511954bc61ffb1a8882498855ba4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -560,6 +560,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
}
|
|
}
|
|
// Paper start
|
|
+ if (playerchunk != null) playerchunk.lastActivity = world.getTime(); // Paper
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
|
|
if (isUrgent) {
|
|
future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair));
|
|
@@ -812,6 +813,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings
|
|
this.world.getMethodProfiler().exit();
|
|
// Paper - replaced by above
|
|
+ final long time = world.getTime(); // Paper
|
|
final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
|
|
Optional<Chunk> optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
|
|
|
|
@@ -897,7 +899,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper
|
|
if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
}
|
|
- }
|
|
+ } else { checkInactiveChunk(playerchunk, time); } // Paper - check inaccessible chunks
|
|
});
|
|
this.world.getMethodProfiler().enter("customSpawners");
|
|
if (flag1) {
|
|
@@ -913,6 +915,37 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.playerChunkMap.g();
|
|
}
|
|
|
|
+ // Paper start - remove inaccessible chunks leaked
|
|
+ private void checkInactiveChunk(PlayerChunk playerchunk, long time) {
|
|
+ int ticketLevel = playerchunk.getTicketLevel();
|
|
+ if (ticketLevel > 33 && ticketLevel == playerchunk.oldTicketLevel &&
|
|
+ (playerchunk.lastActivity == 0 || time - playerchunk.lastActivity > 20*180) &&
|
|
+ playerchunk.location.pair() % 20 == 0 && playerChunkMap.unloadQueue.size() < 100 &&
|
|
+ PlayerChunk.getChunkState(ticketLevel) == PlayerChunk.State.INACCESSIBLE
|
|
+ ) {
|
|
+ ChunkStatus chunkHolderStatus = playerchunk.getChunkHolderStatus();
|
|
+ ChunkStatus desiredStatus = PlayerChunk.getChunkStatus(ticketLevel);
|
|
+ if (chunkHolderStatus != null && !chunkHolderStatus.isAtLeastStatus(desiredStatus)) {
|
|
+ return;
|
|
+ }
|
|
+ if (playerchunk.lastActivity == 0) {
|
|
+ playerchunk.lastActivity = time;
|
|
+ return;
|
|
+ }
|
|
+ playerchunk.lastActivity = time;
|
|
+ Chunk chunk = playerchunk.getChunk();
|
|
+ if ((chunk != null && chunk.isAnyNeighborsLoaded()) || !playerchunk.neighborPriorities.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ long key = playerchunk.location.pair();
|
|
+ ArraySetSorted<Ticket<?>> tickets = playerChunkMap.chunkDistanceManager.tickets.get(key);
|
|
+ if (tickets == null || tickets.isEmpty()) {
|
|
+ playerChunkMap.unloadQueue.add(key);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public String getName() {
|
|
return "ServerChunkCache: " + this.h();
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index b8fe42e8123e972b1ec97b048c35d90118076e66..ecac7b72759a3884020b9c19c58d3db3338e0fc3 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -44,6 +44,7 @@ public class PlayerChunk {
|
|
|
|
long lastAutoSaveTime; // Paper - incremental autosave
|
|
long inactiveTimeStart; // Paper - incremental autosave
|
|
+ long lastActivity; // Paper - fix chunk leak
|
|
|
|
// Paper start - optimise isOutsideOfRange
|
|
// cached here to avoid a map lookup
|
|
@@ -562,6 +563,7 @@ public class PlayerChunk {
|
|
protected void a(PlayerChunkMap playerchunkmap) {
|
|
ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel);
|
|
ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel);
|
|
+ if (oldTicketLevel != ticketLevel) lastActivity = chunkMap.world.getTime(); // Paper - chunk leak
|
|
boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET;
|
|
boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; // Paper - diff on change: (flag1 = new ticket level is in loadable range)
|
|
PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel);
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 2b09437642ec846d025b226692f2290f9bb5b556..b7d8a7487e303c2c44160ec1093987b3e0d175bc 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -639,6 +639,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
}));
|
|
}
|
|
+ playerchunk.lastActivity = world.getTime(); // Paper - chunk leak
|
|
|
|
ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1);
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
|
|
@@ -646,6 +647,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) {
|
|
requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus);
|
|
completablefuture.thenAccept(either -> {
|
|
+ playerchunk.lastActivity = world.getTime(); // Paper - chunk leak
|
|
requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null));
|
|
});
|
|
}
|
|
@@ -871,6 +873,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
while (longiterator.hasNext()) { // Spigot
|
|
long j = longiterator.nextLong();
|
|
longiterator.remove(); // Spigot
|
|
+ ArraySetSorted<Ticket<?>> tickets = chunkDistanceManager.tickets.get(j); // Paper - chunk leak
|
|
+ if (tickets != null && !tickets.isEmpty()) continue; // Paper - ticket got added, don't remove
|
|
PlayerChunk playerchunk = (PlayerChunk) this.updatingChunks.remove(j);
|
|
|
|
if (playerchunk != null) {
|
|
diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java
|
|
index acfe732af5b9f63fc2f6b78499defabe2e73ee45..25b19346fc1c702cc37275d0ec16abbbfacfb418 100644
|
|
--- a/src/main/java/net/minecraft/server/StructureGenerator.java
|
|
+++ b/src/main/java/net/minecraft/server/StructureGenerator.java
|
|
@@ -160,7 +160,7 @@ public abstract class StructureGenerator<C extends WorldGenFeatureConfiguration>
|
|
while (longiterator.hasNext()) {
|
|
long k = longiterator.nextLong();
|
|
IChunkAccess ichunkaccess1 = generatoraccess.getChunkAt(ChunkCoordIntPair.getX(k), ChunkCoordIntPair.getZ(k), ChunkStatus.STRUCTURE_STARTS, false); // CraftBukkit - don't load chunks
|
|
- StructureStart structurestart = ichunkaccess1.a(this.b());
|
|
+ StructureStart structurestart = ichunkaccess1 != null ? ichunkaccess1.a(this.b()) : null; // Paper - make sure not null
|
|
|
|
if (structurestart != null) {
|
|
list.add(structurestart);
|