Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aikar/dd3afadf7baaee5202e6c7d7be0115af to your computer and use it in GitHub Desktop.
Save aikar/dd3afadf7baaee5202e6c7d7be0115af to your computer and use it in GitHub Desktop.
From 414cb1c38d347adcf66a4b4bdfb1a150f647a6a8 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
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