Created
March 30, 2020 06:47
-
-
Save aikar/dd3afadf7baaee5202e6c7d7be0115af to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 414cb1c38d347adcf66a4b4bdfb1a150f647a6a8 Mon Sep 17 00:00:00 2001 | |
From: Aikar <[email protected]> | |
Date: Fri, 27 Mar 2020 20:57:32 -0400 | |
Subject: [PATCH] Improve behavior of main thread blocking chunk load/gens | |
--- | |
.../paper/io/PrioritizedTaskQueue.java | 13 ++-- | |
.../paper/io/chunk/ChunkTaskManager.java | 59 +++++++++++--- | |
.../minecraft/server/ChunkProviderServer.java | 14 ++++ | |
.../net/minecraft/server/ChunkStatus.java | 3 + | |
.../net/minecraft/server/PlayerChunk.java | 76 ++++++++++++++++++- | |
.../net/minecraft/server/PlayerChunkMap.java | 21 +++-- | |
6 files changed, 162 insertions(+), 24 deletions(-) | |
diff --git a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java | |
index 78bd238f4c..97f2e433c4 100644 | |
--- a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java | |
+++ b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java | |
@@ -72,8 +72,11 @@ public class PrioritizedTaskQueue<T extends PrioritizedTaskQueue.PrioritizedTask | |
* This can also be thrown if the queue has shutdown. | |
*/ | |
public void add(final T task) throws IllegalStateException { | |
- task.onQueue(this); | |
- this.queues[task.getPriority()].add(task); | |
+ int priority = task.getPriority(); | |
+ if (priority != COMPLETING_PRIORITY) { | |
+ task.setQueue(this); | |
+ this.queues[priority].add(task); | |
+ } | |
if (this.shutdown.get()) { | |
// note: we're not actually sure at this point if our task will go through | |
throw new IllegalStateException("Queue has shutdown, refusing to execute task " + IOUtil.genericToString(task)); | |
@@ -250,10 +253,8 @@ public class PrioritizedTaskQueue<T extends PrioritizedTaskQueue.PrioritizedTask | |
} | |
} | |
- void onQueue(final PrioritizedTaskQueue queue) { | |
- if (this.queue.getAndSet(queue) != null) { | |
- throw new IllegalStateException("Already queued!"); | |
- } | |
+ void setQueue(final PrioritizedTaskQueue queue) { | |
+ this.queue.set(queue); | |
} | |
/* priority */ | |
diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java | |
index 715a2dd8d2..e8282e9781 100644 | |
--- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java | |
+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java | |
@@ -34,7 +34,9 @@ public final class ChunkTaskManager { | |
private final PrioritizedTaskQueue<ChunkTask> chunkTasks = new PrioritizedTaskQueue<>(); // used if async chunks are disabled in config | |
protected static QueueExecutorThread<ChunkTask>[] globalWorkers; | |
+ protected static QueueExecutorThread<ChunkTask> globalUrgentWorker; | |
protected static PrioritizedTaskQueue<ChunkTask> globalQueue; | |
+ protected static PrioritizedTaskQueue<ChunkTask> globalUrgentQueue; | |
protected static final ConcurrentLinkedQueue<Runnable> CHUNK_WAIT_QUEUE = new ConcurrentLinkedQueue<>(); | |
@@ -116,6 +118,7 @@ public final class ChunkTaskManager { | |
globalWorkers = new QueueExecutorThread[threads]; | |
globalQueue = new PrioritizedTaskQueue<>(); | |
+ globalUrgentQueue = new PrioritizedTaskQueue<>(); | |
for (int i = 0; i < threads; ++i) { | |
globalWorkers[i] = new QueueExecutorThread<>(globalQueue, (long)0.10e6); //0.1ms | |
@@ -127,6 +130,15 @@ public final class ChunkTaskManager { | |
globalWorkers[i].start(); | |
} | |
+ | |
+ globalUrgentWorker = new QueueExecutorThread<>(globalUrgentQueue, (long)0.10e6); //0.1ms | |
+ globalUrgentWorker.setName("Paper Async Chunk Urgent Task Thread"); | |
+ globalUrgentWorker.setPriority(Thread.NORM_PRIORITY+1); | |
+ globalUrgentWorker.setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> { | |
+ PaperFileIOThread.LOGGER.fatal("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable); | |
+ }); | |
+ | |
+ globalUrgentWorker.start(); | |
} | |
/** | |
@@ -377,6 +389,7 @@ public final class ChunkTaskManager { | |
worker.flush(); | |
} | |
} | |
+ globalUrgentWorker.flush(); | |
// flush again since tasks we execute async saves | |
drainChunkWaitQueue(); | |
@@ -409,20 +422,30 @@ public final class ChunkTaskManager { | |
public void raisePriority(final int chunkX, final int chunkZ, final int priority) { | |
final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)); | |
- ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(chunkKey); | |
+ ChunkTask chunkSaveTask = this.chunkSaveTasks.get(chunkKey); | |
if (chunkSaveTask != null) { | |
- final boolean raised = chunkSaveTask.raisePriority(priority); | |
- if (chunkSaveTask.isScheduled() && raised) { | |
- // only notify if we're in queue to be executed | |
- this.internalScheduleNotify(); | |
- } | |
+ // don't bump save into urgent queue | |
+ raiseTaskPriority(chunkSaveTask, priority != PrioritizedTaskQueue.HIGHEST_PRIORITY ? priority : PrioritizedTaskQueue.HIGH_PRIORITY); | |
} | |
ChunkLoadTask chunkLoadTask = this.chunkLoadTasks.get(chunkKey); | |
if (chunkLoadTask != null) { | |
- final boolean raised = chunkLoadTask.raisePriority(priority); | |
- if (chunkLoadTask.isScheduled() && raised) { | |
- // only notify if we're in queue to be executed | |
+ raiseTaskPriority(chunkLoadTask, priority); | |
+ } | |
+ } | |
+ | |
+ private void raiseTaskPriority(ChunkTask task, int priority) { | |
+ final boolean raised = task.raisePriority(priority); | |
+ if (task.isScheduled() && raised) { | |
+ // only notify if we're in queue to be executed | |
+ if (priority == PrioritizedTaskQueue.HIGHEST_PRIORITY) { | |
+ // was in another queue but became urgent later, add to urgent queue and the previous | |
+ // queue will just have to ignore this task if it has already been started. | |
+ // Ultimately, we now have 2 potential queues that can pull it out whoever gets it first | |
+ // but the urgent queue has dedicated thread(s) so it's likely to win.... | |
+ globalUrgentQueue.add(task); | |
+ this.internalScheduleNotifyUrgent(); | |
+ } else { | |
this.internalScheduleNotify(); | |
} | |
} | |
@@ -436,8 +459,14 @@ public final class ChunkTaskManager { | |
// It's important we order the task to be executed before notifying. Avoid a race condition where the worker thread | |
// wakes up and goes to sleep before we actually schedule (or it's just about to sleep) | |
- this.queue.add(task); | |
- this.internalScheduleNotify(); | |
+ if (task.getPriority() == PrioritizedTaskQueue.HIGHEST_PRIORITY) { | |
+ globalUrgentQueue.add(task); | |
+ this.internalScheduleNotifyUrgent(); | |
+ } else { | |
+ this.queue.add(task); | |
+ this.internalScheduleNotify(); | |
+ } | |
+ | |
} | |
protected void internalScheduleNotify() { | |
@@ -452,4 +481,12 @@ public final class ChunkTaskManager { | |
} | |
} | |
+ | |
+ protected void internalScheduleNotifyUrgent() { | |
+ if (globalUrgentWorker == null) { | |
+ return; | |
+ } | |
+ globalUrgentWorker.notifyTasks(); | |
+ } | |
+ | |
} | |
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java | |
index 1dcd0980ec..f81d6bdeba 100644 | |
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java | |
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java | |
@@ -227,6 +227,7 @@ public class ChunkProviderServer extends IChunkProvider { | |
} | |
private long asyncLoadSeqCounter; | |
+ public static boolean IS_CHUNK_LOAD_BLOCKING_MAIN = false; | |
public void getChunkAtAsynchronously(int x, int z, boolean gen, java.util.function.Consumer<Chunk> onComplete) { | |
if (Thread.currentThread() != this.serverThread) { | |
@@ -384,10 +385,18 @@ public class ChunkProviderServer extends IChunkProvider { | |
} | |
gameprofilerfiller.c("getChunkCacheMiss"); | |
+ // Paper start - Async chunks | |
+ boolean prevBlocking = IS_CHUNK_LOAD_BLOCKING_MAIN; | |
+ IS_CHUNK_LOAD_BLOCKING_MAIN = true; | |
+ // Paper end | |
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag); | |
if (!completablefuture.isDone()) { // Paper | |
// Paper start - async chunk io/loading | |
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z)); | |
+ if (playerChunk != null) { | |
+ playerChunk.markChunkUrgent(); | |
+ } | |
this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); | |
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z); | |
// Paper end | |
@@ -397,6 +406,11 @@ public class ChunkProviderServer extends IChunkProvider { | |
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug | |
this.world.timings.chunkAwait.stopTiming(); // Paper | |
} // Paper | |
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z)); | |
+ if (playerChunk != null) { | |
+ playerChunk.clearChunkUrgent(); | |
+ } | |
+ IS_CHUNK_LOAD_BLOCKING_MAIN = prevBlocking;// Paper | |
ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { | |
return ichunkaccess1; | |
}, (playerchunk_failure) -> { | |
diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java | |
index 88f1674616..40ce30cdc2 100644 | |
--- a/src/main/java/net/minecraft/server/ChunkStatus.java | |
+++ b/src/main/java/net/minecraft/server/ChunkStatus.java | |
@@ -169,6 +169,7 @@ public class ChunkStatus { | |
this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; | |
} | |
+ public int getStatusIndex() { return c(); } // Paper - OBFHELPER | |
public int c() { | |
return this.t; | |
} | |
@@ -190,6 +191,7 @@ public class ChunkStatus { | |
return this.w.doWork(this, worldserver, definedstructuremanager, lightenginethreaded, function, ichunkaccess); | |
} | |
+ public int getNeighborRadius() { return this.f(); } // Paper - OBFHELPER | |
public int f() { | |
return this.x; | |
} | |
@@ -217,6 +219,7 @@ public class ChunkStatus { | |
return this.z; | |
} | |
+ public boolean isAtLeastStatus(ChunkStatus chunkstatus) { return b(chunkstatus); } // Paper - OBFHELPER | |
public boolean b(ChunkStatus chunkstatus) { | |
return this.c() >= chunkstatus.c(); | |
} | |
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java | |
index 9f8818c2d4..7de172a9a5 100644 | |
--- a/src/main/java/net/minecraft/server/PlayerChunk.java | |
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java | |
@@ -1,6 +1,8 @@ | |
package net.minecraft.server; | |
import com.mojang.datafixers.util.Either; | |
+import net.minecraft.server.Raid.Status; | |
+ | |
import java.util.List; | |
import java.util.Optional; | |
import java.util.concurrent.CompletableFuture; | |
@@ -43,6 +45,71 @@ public class PlayerChunk { | |
long lastAutoSaveTime; // Paper - incremental autosave | |
long inactiveTimeStart; // Paper - incremental autosave | |
+ // Paper start | |
+ int chunkPriority = -1; | |
+ int lastChunkPriority = -1; | |
+ boolean isUrgent = false; | |
+ java.util.List<PlayerChunk> urgentNeighbors = new java.util.ArrayList<>(); | |
+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) { | |
+ if (isUrgent && !neighbor.isUrgent) { | |
+ neighbor.markChunkUrgent(); | |
+ this.urgentNeighbors.add(neighbor); | |
+ } | |
+ } | |
+ | |
+ public void markChunkUrgent() { | |
+ if (!this.isUrgent) { | |
+ this.isUrgent = true; | |
+ this.lastChunkPriority = this.chunkPriority; | |
+ this.chunkPriority = Math.max(0, this.ticketLevel - 20); | |
+ int x = location.x; | |
+ int z = location.z; | |
+ IChunkAccess chunk = getAvailableChunkNow(); | |
+ ChunkStatus curHolderStatus = this.getChunkHolderStatus(); | |
+ ChunkStatus status = chunk != null ? chunk.getChunkStatus() : ChunkStatus.EMPTY; | |
+ if (ChunkStatus.FULL.equals(status)) { | |
+ return; | |
+ } | |
+ if (status == ChunkStatus.EMPTY) { | |
+ // If it's started already, bump it, else it'll start at highest when it does start | |
+ this.chunkMap.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); | |
+ } | |
+ | |
+ ChunkStatus nextStatus = getNextStatus(curHolderStatus); | |
+ int i = Math.max(0, nextStatus.getNeighborRadius()); | |
+ for (int cx = -i; i > 0 && cx <= i; ++cx) { | |
+ for (int cz = -i; cz <= i; ++cz) { | |
+ if (cx == 0 && cz == 0) { | |
+ continue; | |
+ } | |
+ ChunkStatus neededStatus = this.chunkMap.getNeededStatusByRadius(nextStatus, Math.max(Math.abs(cx), Math.abs(cz))); | |
+ PlayerChunk neighbor = this.chunkMap.getUpdatingChunk(ChunkCoordIntPair.asLong(x + cz, z + cx)); | |
+ if (neighbor == null) { | |
+ continue; | |
+ } | |
+ IChunkAccess neighborChunk = neighbor.getAvailableChunkNow(); | |
+ ChunkStatus neighborCurrentStatus = neighborChunk != null ? neighborChunk.getChunkStatus() : ChunkStatus.EMPTY; | |
+ if (!neighborCurrentStatus.isAtLeastStatus(neededStatus) || nextStatus == ChunkStatus.LIGHT) { | |
+ neighbor.markChunkUrgent(); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ } | |
+ | |
+ public void clearChunkUrgent() { | |
+ if (this.isUrgent) { | |
+ this.chunkPriority = this.lastChunkPriority; | |
+ this.lastChunkPriority = -1; | |
+ this.isUrgent = false; | |
+ for (PlayerChunk urgentNeighbor : this.urgentNeighbors) { | |
+ urgentNeighbor.clearChunkUrgent(); | |
+ } | |
+ this.urgentNeighbors.clear(); | |
+ } | |
+ } | |
+ // Paper end | |
+ | |
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) { | |
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size()); | |
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; | |
@@ -127,7 +194,6 @@ public class PlayerChunk { | |
} | |
return null; | |
} | |
- | |
public ChunkStatus getChunkHolderStatus() { | |
for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) { | |
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.getStatusFutureUnchecked(curr); | |
@@ -139,6 +205,12 @@ public class PlayerChunk { | |
} | |
return null; | |
} | |
+ public static ChunkStatus getNextStatus(ChunkStatus status) { | |
+ if (status == ChunkStatus.FULL) { | |
+ return status; | |
+ } | |
+ return CHUNK_STATUSES.get(status.getStatusIndex() + 1); | |
+ } | |
// Paper end | |
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) { | |
@@ -351,7 +423,7 @@ public class PlayerChunk { | |
} | |
public int k() { | |
- return this.n; | |
+ return this.chunkPriority != -1 ? this.chunkPriority : this.n; // Paper - allow overriding priority | |
} | |
private void d(int i) { | |
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java | |
index f9e843288a..5520142fa5 100644 | |
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java | |
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java | |
@@ -245,6 +245,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { | |
List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList(); | |
int j = chunkcoordintpair.x; | |
int k = chunkcoordintpair.z; | |
+ PlayerChunk requestingNeighbor = this.requestingNeighbor; // Paper | |
for (int l = -i; l <= i; ++l) { | |
for (int i1 = -i; i1 <= i; ++i1) { | |
@@ -262,6 +263,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { | |
} | |
ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1); | |
+ if (requestingNeighbor != null) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper | |
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this); | |
list.add(completablefuture); | |
@@ -705,23 +707,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { | |
}; | |
CompletableFuture<NBTTagCompound> chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z); | |
+ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair()); | |
+ boolean isBlockingMain = playerChunk != null && playerChunk.isUrgent; | |
+ int priority = isBlockingMain ? com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY : com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY; | |
if (chunkSaveFuture != null) { | |
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, | |
- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture); | |
- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY); | |
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain, chunkSaveFuture); | |
} else { | |
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, | |
- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); | |
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain); | |
} | |
+ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority); | |
return ret; | |
// Paper end | |
} | |
+ private PlayerChunk requestingNeighbor; // Paper | |
private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) { | |
ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); | |
+ PlayerChunk prevNeighbor = requestingNeighbor; // Paper | |
+ this.requestingNeighbor = playerchunk; // Paper | |
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, chunkstatus.f(), (i) -> { | |
return this.a(chunkstatus, i); | |
}); | |
+ this.requestingNeighbor = prevNeighbor; // Paper | |
this.world.getMethodProfiler().c(() -> { | |
return "chunkGenerate " + chunkstatus.d(); | |
@@ -761,6 +768,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { | |
})); | |
} | |
+ public ChunkStatus getNeededStatusByRadius(ChunkStatus chunkstatus, int i) { return a(chunkstatus, i); } // Paper - OBFHELPER | |
private ChunkStatus a(ChunkStatus chunkstatus, int i) { | |
ChunkStatus chunkstatus1; | |
@@ -885,9 +893,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { | |
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> a(PlayerChunk playerchunk) { | |
ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); | |
+ PlayerChunk prevNeighbor = this.requestingNeighbor; // Paper | |
+ this.requestingNeighbor = playerchunk; // Paper | |
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, 1, (i) -> { | |
return ChunkStatus.FULL; | |
}); | |
+ this.requestingNeighbor = prevNeighbor; // Paper | |
CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture1 = completablefuture.thenApplyAsync((either) -> { | |
return either.flatMap((list) -> { | |
Chunk chunk = (Chunk) list.get(list.size() / 2); | |
-- | |
2.25.1 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment