aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/1036-Write-SavedData-IO-async.patch
blob: 2227ee25207c229ffc883ccd37f27e0216a6939f (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
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/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 9e282fabc6815393e4183aebefe112305bf2ff2c..74c39ffd7add898d8604cd23381811251e55f882 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -367,6 +367,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
 
     public void close(boolean save) throws IOException {
         ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(save, true); // Paper - rewrite chunk system
+        // Paper start - Write SavedData IO async
+        try {
+            this.dataStorage.close();
+        } catch (final IOException e) {
+            LOGGER.error("Failed to close persistent world data", e);
+        }
+        // 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 1d74a728c03e2c6ffe0e795a5f24eac471a75157..46e8dd8dae25e1b2124e9c8031844fbe96eb6e1a 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1373,7 +1373,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
                 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"));
             }
@@ -1404,12 +1404,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf.
         // 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 0382b6597a130d746f8954a93a756a9d1ac81d50..cb39c629af1827078f35904a373d35a63fea17ff 100644
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@@ -116,7 +116,13 @@ public class WorldUpgrader {
         (new WorldUpgrader.PoiUpgrader(this)).upgrade();
         WorldUpgrader.LOGGER.info("Upgrading blocks");
         (new WorldUpgrader.ChunkUpgrader()).upgrade();
-        this.overworldDataStorage.save();
+        // Paper start - Write SavedData IO async
+        try {
+            this.overworldDataStorage.close();
+        } catch (final IOException e) {
+            LOGGER.error("Failed to close persistent world data", e);
+        }
+        // Paper end - Write SavedData IO async
         i = Util.getMillis() - i;
         WorldUpgrader.LOGGER.info("World optimizaton finished after {} seconds", i / 1000L);
         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 9cc3850bb70dfbcf342651360314a19fd9ea3ecc..4cbb943b702baaeb8bfd2b558cc848e719cf095d 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java
@@ -30,20 +30,36 @@ public abstract class SavedData {
         return this.dirty;
     }
 
+    // Paper start - Write SavedData IO async - joining is evil, but we assume the old blocking behavior here just for safety
+    @io.papermc.paper.annotation.DoNotUse
     public void save(File file, HolderLookup.Provider registryLookup) {
+        save(file, registryLookup, null).join();
+    }
+
+    public java.util.concurrent.CompletableFuture<Void> save(File file, HolderLookup.Provider registryLookup, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) {
+        // Paper end - Write SavedData IO async
         if (this.isDirty()) {
             CompoundTag compoundTag = new CompoundTag();
             compoundTag.put("data", this.save(new CompoundTag(), registryLookup));
             NbtUtils.addCurrentDataVersion(compoundTag);
 
+            Runnable writeRunnable = () -> { // Paper - Write SavedData IO async
             try {
                 NbtIo.writeCompressed(compoundTag, file.toPath());
             } catch (IOException var5) {
                 LOGGER.error("Could not save data {}", this, var5);
             }
+            }; // 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>(
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 7d98d2911e9d6ec429ce7df1f1f2650c7ea32325..6e23e69abd56eeda3b52a22019e1b74ae10682e7 100644
--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
+++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java
@@ -23,17 +23,19 @@ 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 HolderLookup.Provider registries;
     private final File dataFolder;
+    protected final java.util.concurrent.ExecutorService ioExecutor; // Paper - Write SavedData IO async
 
     public DimensionDataStorage(File directory, DataFixer dataFixer, HolderLookup.Provider registryLookup) {
         this.fixerUpper = dataFixer;
         this.dataFolder = directory;
         this.registries = registryLookup;
+        this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + dataFolder.getParent() + " - %d").setDaemon(true).build()); // Paper - Write SavedData IO async
     }
 
     private File getDataFile(String id) {
@@ -123,10 +125,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), this.registries);
+                // Paper start - Write SavedData IO async
+                final java.util.concurrent.CompletableFuture<Void> save = state.save(this.getDataFile(id), this.registries, this.ioExecutor);
+                if (!async) {
+                    save.join();
+                }
+                // Paper end - Write SavedData IO async
             }
         });
     }