aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0068-Chunk-save-queue-improvements.patch
blob: 01ba873b3ace54711fd5d5efd0d65678b06ba9de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
From 240543e6e7c364a4513e4cd4ca8d2711c5d0f97d 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 8c8bc4c86..b2fa32e41 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -203,4 +203,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 79cb3953b..7f3e874ba 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -12,14 +12,17 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nullable;
+import java.util.concurrent.ConcurrentLinkedQueue; // Paper
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
 
+    private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>(); // Paper - Chunk queue improvements
+    private final Object lock = new Object(); // Paper - Chunk queue improvements
     private static final Logger a = LogManager.getLogger();
     private final Map<ChunkCoordIntPair, NBTTagCompound> b = new ConcurrentHashMap();
-    private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(new ConcurrentHashMap());
+    //private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(new ConcurrentHashMap()); // Paper - Chunk queue improvements
     private final File d;
     private final DataConverterManager e;
     private boolean f;
@@ -33,11 +36,11 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
     public boolean chunkExists(World world, int i, int j) {
         ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
 
-        if (this.c.contains(chunkcoordintpair)) {
+        //if (this.c.contains(chunkcoordintpair)) { // Paper - Chunk queue improvements
             if (this.b.containsKey(chunkcoordintpair)) {
                 return true;
             }
-        }
+        //} // Paper - Chunk queue improvements
 
         // Paper start - Don't create region files when checking that they exist
         final RegionFile region = RegionFileCache.a(this.d, i, j, false);
@@ -151,35 +154,32 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
     }
 
     protected void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) {
-        if (!this.c.contains(chunkcoordintpair)) {
+        synchronized (lock) {  // Paper - Chunk queue improvements
             this.b.put(chunkcoordintpair, nbttagcompound);
         }
+        queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
 
         FileIOThread.a().a(this);
     }
 
     public boolean c() {
-        // CraftBukkit start
-        Iterator<Map.Entry<ChunkCoordIntPair, NBTTagCompound>> iter = this.b.entrySet().iterator();
-        if (!iter.hasNext()) {
-            // CraftBukkit end
+        // Paper start - Chunk queue improvements
+        QueuedChunk chunk = queue.poll();
+        if (chunk == null) {
+            // Paper - end
             if (this.f) {
                 ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", new Object[] { this.d.getName()});
             }
 
             return false;
         } else {
-            // CraftBukkit start
-            Map.Entry<ChunkCoordIntPair, NBTTagCompound> entry = iter.next();
-            iter.remove(); // Pop single entry
-            ChunkCoordIntPair chunkcoordintpair = entry.getKey();
-            // CraftBukkit end
+            ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
 
             boolean flag;
 
             try {
-                this.c.add(chunkcoordintpair);
-                NBTTagCompound nbttagcompound = (NBTTagCompound) entry.getValue(); // CraftBukkit
+                //this.c.add(chunkcoordintpair);
+                NBTTagCompound nbttagcompound = chunk.compound; // Paper - Chunk queue improvements
 
                 if (nbttagcompound != null) {
                     try {
@@ -188,10 +188,11 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
                         ChunkRegionLoader.a.error("Failed to save chunk", exception);
                     }
                 }
+                synchronized (lock) { if (this.b.get(chunkcoordintpair) == nbttagcompound) { this.b.remove(chunkcoordintpair); } }// Paper - This will not equal if a newer version is still pending
 
                 flag = true;
             } finally {
-                this.c.remove(chunkcoordintpair);
+                //this.c.remove(chunkcoordintpair); // Paper
             }
 
             return flag;
@@ -556,4 +557,16 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
             return entity;
         }
     }
+
+    // Paper start - Chunk queue improvements
+    private static class QueuedChunk {
+        public ChunkCoordIntPair coords;
+        public NBTTagCompound compound;
+
+        public QueuedChunk(ChunkCoordIntPair coords, NBTTagCompound compound) {
+            this.coords = coords;
+            this.compound = compound;
+        }
+    }
+    // Paper end
 }
diff --git a/src/main/java/net/minecraft/server/FileIOThread.java b/src/main/java/net/minecraft/server/FileIOThread.java
index acfdd52dc..fdbaf5fbd 100644
--- a/src/main/java/net/minecraft/server/FileIOThread.java
+++ b/src/main/java/net/minecraft/server/FileIOThread.java
@@ -39,11 +39,15 @@ public class FileIOThread implements Runnable {
                 ++this.d;
             }
 
-            try {
-                Thread.sleep(this.e ? 0L : 10L);
-            } catch (InterruptedException interruptedexception) {
-                interruptedexception.printStackTrace();
+            // Paper start - Add toggle
+            if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) {
+                try {
+                    Thread.sleep(this.e ? 0L : 2L);
+                } catch (InterruptedException interruptedexception) {
+                    interruptedexception.printStackTrace();
+                }
             }
+            // Paper end
         }
 
         if (this.b.isEmpty()) {
-- 
2.12.0.windows.1