aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0999-Write-SavedData-IO-async.patch
blob: fc9b21134719f64cec6d4148c4c72ebeb4cd05b9 (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
182
183
184
185
186
187
188
189
190
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cryptite <cryptite@gmail.com>
Date: Tue, 27 Jun 2023 11:35:52 -0500
Subject: [PATCH] Write SavedData IO async

Co-Authored-By: Shane Freeder <theboyetronic@gmail.com>

diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
index 513833c2ea23df5b079d157bc5cb89d5c9754c0b..9017907c0ec67a37a506f09b7e4499cef7885279 100644
--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
@@ -97,6 +97,15 @@ public class ThreadedWorldUpgrader {
             }
 
             this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5));
+            // Paper start - Write SavedData IO async
+            this.threadPool.execute(() -> {
+                try {
+                    worldPersistentData.close();
+                } catch (IOException exception) {
+                    LOGGER.error("Failed to close persistent world data", exception);
+                }
+            });
+            // Paper end - Write SavedData IO async
         }
         this.threadPool.shutdown();
 
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 5afbb5b307cc67d86dd916dc8f7521d5d021e056..4e0f80b26f1a1703184e38d739996b9919699fec 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -452,6 +452,13 @@ public class ServerChunkCache extends ChunkSource {
 
     public void close(boolean save) { // Paper - rewrite chunk system
         this.level.chunkTaskScheduler.chunkHolderManager.close(save, true); // Paper - rewrite chunk system
+        // Paper start - Write SavedData IO async
+        try {
+            this.dataStorage.close();
+        } catch (IOException exception) {
+            LOGGER.error("Failed to close persistent world data", exception);
+        }
+        // Paper end - Write SavedData IO async
     }
 
     // CraftBukkit start - modelled on below
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 6f497b95fd870a32c56590c00b2b39f88c51ecb9..4f2d30e8ffe228952db64d0122a8958e33f841fa 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1318,7 +1318,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
 
         try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) {
             if (doFull) {
-                this.saveLevelData();
+                this.saveLevelData(true); // Paper - Write SavedData IO async
             }
 
             this.timings.worldSaveChunks.startTiming(); // Paper
@@ -1354,7 +1354,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
                 progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
             }
 
-            this.saveLevelData();
+            this.saveLevelData(!close); // Paper - Write SavedData IO async
             if (progressListener != null) {
                 progressListener.progressStage(Component.translatable("menu.savingChunks"));
             }
@@ -1377,12 +1377,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
         // CraftBukkit end
     }
 
-    private void saveLevelData() {
+    private void saveLevelData(boolean async) { // Paper - Write SavedData IO async
         if (this.dragonFight != null) {
             this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
         }
 
-        this.getChunkSource().getDataStorage().save();
+        this.getChunkSource().getDataStorage().save(async); // Paper - Write SavedData IO async
     }
 
     public <T extends Entity> List<? extends T> getEntities(EntityTypeTest<Entity, T> filter, Predicate<? super T> predicate) {
diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
index f2a7cb6ebed7a4b4019a09af2a025f624f6fe9c9..77dd632a266f4abed30b87b7909d77857c01e316 100644
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@@ -224,7 +224,13 @@ public class WorldUpgrader {
                 }
             }
 
-            this.overworldDataStorage.save();
+            // Paper start - Write SavedData IO async
+            try {
+                this.overworldDataStorage.close();
+            } catch (IOException exception) {
+                LOGGER.error("Failed to close persistent world data", exception);
+            }
+            // Paper end - Write SavedData IO async
             i = Util.getMillis() - i;
             WorldUpgrader.LOGGER.info("World optimizaton finished after {} ms", i);
             this.finished = true;
diff --git a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
index 697df9a9f050c0130246ce2b08a859965bddf184..0655a26a58b3df19d81b18abf6b8ab81fd000ef7 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
@@ -29,20 +29,34 @@ public abstract class SavedData {
         return this.dirty;
     }
 
+    @io.papermc.paper.annotation.DoNotUse // Paper - Write SavedData IO async - This is dead
     public void save(File file) {
+        save(file, null).join(); // Paper - Write SavedData IO async - joining is evil, but we assume the old blocking behavior here just for safety
+    }
+
+    public java.util.concurrent.CompletableFuture<Void> save(File file, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) { // Paper - Write SavedData IO async
         if (this.isDirty()) {
             CompoundTag compoundTag = new CompoundTag();
             compoundTag.put("data", this.save(new CompoundTag()));
             NbtUtils.addCurrentDataVersion(compoundTag);
 
+            Runnable writeRunnable = () -> { // Paper - Write SavedData IO async
             try {
                 NbtIo.writeCompressed(compoundTag, file.toPath());
             } catch (IOException var4) {
                 LOGGER.error("Could not save data {}", this, var4);
             }
+            }; // Paper - Write SavedData IO async
 
             this.setDirty(false);
+            // Paper start - Write SavedData IO async
+            if (ioExecutor == null) {
+                return java.util.concurrent.CompletableFuture.runAsync(writeRunnable); // No executor, just use common pool
+            }
+            return java.util.concurrent.CompletableFuture.runAsync(writeRunnable, ioExecutor);
         }
+        return java.util.concurrent.CompletableFuture.completedFuture(null);
+        // Paper end - Write SavedData IO async
     }
 
     public static record Factory<T extends SavedData>(Supplier<T> constructor, Function<CompoundTag, T> deserializer, DataFixTypes type) {
diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
index de44680d9bc34c6ce22e12c3008019c8c6803437..b89bd2d046e99d0c4848579a5ab4385453358b89 100644
--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
+++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
@@ -20,15 +20,18 @@ import net.minecraft.util.datafix.DataFixTypes;
 import net.minecraft.world.level.saveddata.SavedData;
 import org.slf4j.Logger;
 
-public class DimensionDataStorage {
+public class DimensionDataStorage implements java.io.Closeable { // Paper - Write SavedData IO async
     private static final Logger LOGGER = LogUtils.getLogger();
     public final Map<String, SavedData> cache = Maps.newHashMap();
     private final DataFixer fixerUpper;
     private final File dataFolder;
+    protected final java.util.concurrent.ExecutorService ioExecutor; // Paper - Write SavedData IO async
 
     public DimensionDataStorage(File directory, DataFixer dataFixer) {
         this.fixerUpper = dataFixer;
         this.dataFolder = directory;
+        String worldFolder = dataFolder.getParent(); // Paper - Write SavedData IO async
+        this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + worldFolder + " - %d").setDaemon(true).build()); // Paper - Write SavedData IO async
     }
 
     private File getDataFile(String id) {
@@ -118,10 +121,23 @@ public class DimensionDataStorage {
         return bl;
     }
 
-    public void save() {
+    // Paper start - Write SavedData IO async
+    @Override
+    public void close() throws IOException {
+        save(false);
+        this.ioExecutor.shutdown();
+    }
+    // Paper end - Write SavedData IO async
+
+    public void save(boolean async) { // Paper - Write SavedData IO async
         this.cache.forEach((id, state) -> {
             if (state != null) {
-                state.save(this.getDataFile(id));
+                // Paper start - Write SavedData IO async
+                final java.util.concurrent.CompletableFuture<Void> save = state.save(this.getDataFile(id), ioExecutor);
+                if (!async) {
+                    save.join();
+                }
+                // Paper end - Write SavedData IO async
             }
         });
     }