testserver/Spigot-Server-Patches/0062-Chunk-save-queue-impro...

189 lines
9.5 KiB
Diff

From ac2e096b98ab349ce20eb01bb7543fa3aaf4f4ee Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 4 Mar 2016 18:18:37 -0600
Subject: [PATCH] Chunk save queue improvements
For some unknown reason, Minecraft is sleeping 10ms between every single chunk being saved to disk.
Under high chunk load/unload activity (lots of movement / teleporting), this causes the chunk unload queue
to build up in size.
This has multiple impacts:
1) Performance of the unload queue itself - The save thread is pretty ineffecient for how it accesses it
By letting the queue get larger, checking and popping work off the queue can get less performant.
2) Performance of chunk loading - As with #1, chunk loads also have to check this queue when loading
chunk data so that it doesn't load stale data if new data is pending write to disk.
3) Memory Usage - The entire chunk has been serialized to NBT, and now sits in this queue. This leads to
elevated memory usage, and then the objects used in the serialization sit around longer than needed,
resulting in promotion to Old Generation instead of dying young.
To optimize this, we change the entire unload queue to be a proper queue. This improves the behavior of popping
the first queued chunk off, instead of abusing iterators like Mojang was doing.
This also improves reliability of chunk saving, as the previous hack job had a race condition that could
fail to save some chunks.
Then finally, Sleeping will by default be removed, but due to known issues with 1.9, a config option was added.
But if sleeps are to remain enabled, we at least lower the sleep interval so it doesn't have as much negative impact.
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 36689db74..3898ad8fa 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -204,4 +204,10 @@ public class PaperConfig {
private static void chunkLoadThreads() {
minChunkLoadThreads = Math.min(6, getInt("settings.min-chunk-load-threads", 2)); // Keep people from doing stupid things with max of 6
}
+
+ public static boolean enableFileIOThreadSleep;
+ private static void enableFileIOThreadSleep() {
+ enableFileIOThreadSleep = getBoolean("settings.sleep-between-chunk-saves", false);
+ if (enableFileIOThreadSleep) Bukkit.getLogger().info("Enabled sleeping between chunk saves, beware of memory issues");
+ }
}
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index 3a0e52d88..8701777cc 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;
+import java.util.concurrent.ConcurrentLinkedQueue; // Paper
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// Spigot start
@@ -34,8 +35,21 @@ import org.spigotmc.SupplierUtils;
public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
+ // Paper start - Chunk queue improvements
+ private static class QueuedChunk {
+ public ChunkCoordIntPair coords;
+ public Supplier<NBTTagCompound> compoundSupplier;
+
+ public QueuedChunk(ChunkCoordIntPair coords, Supplier<NBTTagCompound> compoundSupplier) {
+ this.coords = coords;
+ this.compoundSupplier = compoundSupplier;
+ }
+ }
+ private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>();
+ // Paper end
+
private static final Logger a = LogManager.getLogger();
- private final Object2ObjectMap<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = Object2ObjectMaps.synchronize(new Object2ObjectLinkedOpenHashMap()); // Spigot
+ private final Object2ObjectMap<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = new Object2ObjectLinkedOpenHashMap(); // Spigot // Paper - remove synchronized
private final File c;
private final DataFixer d;
private PersistentStructureLegacy e;
@@ -285,7 +299,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
}
};
} else {
- if (this.b.containsKey(chunkcoordintpair) && this.a(this.b.get(chunkcoordintpair).get()) == ChunkStatus.Type.LEVELCHUNK || this.a(this.b(world, chunkcoordintpair.x, chunkcoordintpair.z)) == ChunkStatus.Type.LEVELCHUNK) {
+ // Paper start
+ Supplier<NBTTagCompound> existingSave;
+ synchronized (this) {
+ existingSave = this.b.get(chunkcoordintpair);
+ }
+ if (existingSave != null && this.a(existingSave.get()) == ChunkStatus.Type.LEVELCHUNK || this.a(this.b(world, chunkcoordintpair.x, chunkcoordintpair.z)) == ChunkStatus.Type.LEVELCHUNK) { // Paper - extract existingSave to synchronized lookup
+ // Paper end
return;
}
@@ -296,8 +316,8 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
}
};
}
-
- this.a(chunkcoordintpair, SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
+ this.a(chunkcoordintpair, SupplierUtils.createUnivaluedSupplier(completion, unloaded)); // Paper - Remove save queue target size
+ // Paper end
// Spigot end
} catch (Exception exception) {
ChunkRegionLoader.a.error("Failed to save chunk", exception);
@@ -306,18 +326,22 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
}
protected synchronized void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
+ queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
this.b.put(chunkcoordintpair, nbttagcompound);
FileIOThread.a().a(this);
}
- public synchronized boolean a() {
+ public boolean a() { // Paper - remove synchronized
// CraftBukkit start
return this.processSaveQueueEntry(false);
}
- private synchronized boolean processSaveQueueEntry(boolean logCompletion) {
- Iterator<Map.Entry<ChunkCoordIntPair, Supplier<NBTTagCompound>>> iter = this.b.entrySet().iterator(); // Spigot
- if (!iter.hasNext()) {
+ private boolean processSaveQueueEntry(boolean logCompletion) { // Paper - dont synchronize during save
+ // CraftBukkit start
+ // Paper start - Chunk queue improvements
+ QueuedChunk chunk = queue.poll();
+ if (chunk == null) {
+ // Paper - end
if (logCompletion) {
// CraftBukkit end
ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.c.getName());
@@ -325,17 +349,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
return false;
} else {
- // CraftBukkit start
- Map.Entry<ChunkCoordIntPair, Supplier<NBTTagCompound>> entry = iter.next(); // Spigot
- ChunkCoordIntPair chunkcoordintpair = entry.getKey();
- Supplier<NBTTagCompound> value = entry.getValue(); // Spigot
- // CraftBukkit end
+ ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
boolean flag;
try {
// NBTTagCompound nbttagcompound = (NBTTagCompound) this.b.get(chunkcoordintpair); // CraftBukkit
- NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(value); // Spigot
+ NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(chunk.compoundSupplier); // Spigot // Paper
if (nbttagcompound != null) {
try {
@@ -347,7 +367,14 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
flag = true;
} finally {
- this.b.remove(chunkcoordintpair, value); // CraftBukkit // Spigot
+ // Paper start - only synchronize here
+ synchronized (this) {
+ // This will not equal if a newer version is still pending - wait until newest is saved to remove
+ if (this.b.get(chunkcoordintpair) == chunk.compoundSupplier) {
+ this.b.remove(chunkcoordintpair);
+ }
+ }
+ // Paper start
}
return flag;
diff --git a/src/main/java/net/minecraft/server/FileIOThread.java b/src/main/java/net/minecraft/server/FileIOThread.java
index 34312667a..549fab9a5 100644
--- a/src/main/java/net/minecraft/server/FileIOThread.java
+++ b/src/main/java/net/minecraft/server/FileIOThread.java
@@ -43,11 +43,12 @@ public class FileIOThread implements Runnable {
++this.e;
}
+ if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) { // Paper
try {
- Thread.sleep(this.f ? 0L : 10L);
+ Thread.sleep(this.f ? 0L : 1L); // Paper
} catch (InterruptedException interruptedexception) {
interruptedexception.printStackTrace();
- }
+ }} // Paper
}
if (this.c.isEmpty()) {
--
2.18.0