From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sat, 18 Apr 2020 04:36:11 -0400 Subject: [PATCH] Fix Chunk Post Processing deadlock risk See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6 as part of post processing a chunk, we can call ChunkConverter. ChunkConverter then kicks off major physics updates, and when blocks that have connections across chunk boundaries occur, a recursive risk can occur where A updates a block that triggers a physics request. That physics request may trigger a chunk request, that then enqueues a task into the Mailbox ChunkTaskQueueSorter. If anything requests that same chunk that is in the middle of conversion, it's mailbox queue is going to be held up, so the subsequent chunk request will be unable to proceed. We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into the executor so that the mailbox ChunkQueue is now considered empty. This successfully fixed a reoccurring and highly reproducible crash for heightmaps. diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 3afadbd25cb002966869db6e07cd26cf6d52646b..71d76ce0d873c665597a7d28f6caea5bf059e796 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -182,6 +182,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }; // CraftBukkit end + final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper // Paper start - distance maps private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); @@ -1026,16 +1027,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider }); CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { return either.mapLeft((list) -> { - return (LevelChunk) list.get(list.size() / 2); - }); - }, (runnable) -> { - this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); - }).thenApplyAsync((either) -> { - return either.ifLeft((chunk) -> { + // Paper start - revert 1.18.2 diff + final LevelChunk chunk = (LevelChunk) list.get(list.size() / 2); chunk.postProcessGeneration(); this.level.startTickingChunk(chunk); + return chunk; }); - }, this.mainThreadExecutor); + }, (runnable) -> { + this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request. + }); // Paper end - revert 1.18.2 diff completablefuture1.thenAcceptAsync((either) -> { either.ifLeft((chunk) -> { diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index 509b2ee115584ce80717cc12a7ab548d103b4b92..42b4214a1319691e9a6cb0c5fafaeeff821f3f99 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -1138,6 +1138,7 @@ public class ServerChunkCache extends ChunkSource { return super.pollTask() || execChunkTask; // Paper } } finally { + chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter chunkMap.callbackExecutor.run(); } // CraftBukkit end