From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar 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..5254e436ce42dd2ab5e0aac5168a9f1f92c9da26 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -898,6 +898,33 @@ public class ChunkProviderServer extends IChunkProvider { if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper } } + // Paper start - remove inaccessible chunks leaked + else if (playerchunk.getTicketLevel() == playerchunk.oldTicketLevel && + playerChunkMap.unloadQueue.size() < 100 && + (playerchunk.lastActivity == 0 || world.getTime() - playerchunk.lastActivity > 20*180) && + PlayerChunk.getChunkState(playerchunk.getTicketLevel()) == PlayerChunk.State.INACCESSIBLE + ) { + ChunkStatus chunkHolderStatus = playerchunk.getChunkHolderStatus(); + ChunkStatus desiredStatus = PlayerChunk.getChunkStatus(playerchunk.getTicketLevel()); + if (chunkHolderStatus != null && !chunkHolderStatus.isAtLeastStatus(desiredStatus)) { + return; + } + if (playerchunk.lastActivity == 0) { + playerchunk.lastActivity = world.getTime(); + return; + } + playerchunk.lastActivity = world.getTime(); + Chunk chunk = playerchunk.getChunk(); + if ((chunk != null && chunk.isAnyNeighborsLoaded()) || !playerchunk.neighborPriorities.isEmpty()) { + return; + } + long key = playerchunk.location.pair(); + ArraySetSorted> tickets = playerChunkMap.chunkDistanceManager.tickets.get(key); + if (tickets == null || tickets.isEmpty()) { + playerChunkMap.unloadQueue.add(key); + } + // Paper end + } }); this.world.getMethodProfiler().enter("customSpawners"); if (flag1) { 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> 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> 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) {