From 4e672d574c5eeacf58971c6125bc8e83945b7457 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 4 Jan 2024 12:52:03 +0000 Subject: [PATCH] Ensure that we can track saves uses a thread per dim store, this is not ideal but generally not a huge risk, maybe we could handle this on the shared IO pool but it's generally not a big deal or likely worth the effort --- .../0982-Write-SavedData-IO-async.patch | 109 ---------- .../1060-Write-SavedData-IO-async.patch | 187 ++++++++++++++++++ 2 files changed, 187 insertions(+), 109 deletions(-) delete mode 100644 patches/server/0982-Write-SavedData-IO-async.patch create mode 100644 patches/server/1060-Write-SavedData-IO-async.patch diff --git a/patches/server/0982-Write-SavedData-IO-async.patch b/patches/server/0982-Write-SavedData-IO-async.patch deleted file mode 100644 index bbf5a503dc92..000000000000 --- a/patches/server/0982-Write-SavedData-IO-async.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Tue, 27 Jun 2023 11:35:52 -0500 -Subject: [PATCH] Write SavedData IO async - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 18aac3da3c88f33b1a71a5920a8daa27e9723913..544dd0116113690fd8d68f4d6154c8af750f03ca 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1429,7 +1429,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 - } - - this.timings.worldSaveChunks.startTiming(); // Paper -@@ -1465,7 +1465,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); - } - -- this.saveLevelData(); -+ this.saveLevelData(!close); // Paper - if (progressListener != null) { - progressListener.progressStage(Component.translatable("menu.savingChunks")); - } -@@ -1488,12 +1488,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit end - } - -- private void saveLevelData() { -+ private void saveLevelData(boolean async) { // Paper - if (this.dragonFight != null) { - this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit - } - -- this.getChunkSource().getDataStorage().save(); -+ this.getChunkSource().getDataStorage().save(async); // Paper - } - - public List getEntities(EntityTypeTest filter, Predicate predicate) { -diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -index e0bfeebeaac1aaea64bc07cdfdf7790e3e43ca7b..97a7e9e79d9ea69ef4a6b08447805c97870df280 100644 ---- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -@@ -224,7 +224,7 @@ public class WorldUpgrader { - } - } - -- this.overworldDataStorage.save(); -+ this.overworldDataStorage.save(false); // Paper - 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 353e602d476beea23e591ad770227c5d6c1e97fa..366930be719415b0674de2b41c4413e1208c2687 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java -@@ -27,16 +27,31 @@ public abstract class SavedData { - } - - public void save(File file) { -+ // Paper start -+ save(file, false); -+ } -+ -+ public void save(File file, boolean async) { // Paper end - if (this.isDirty()) { - CompoundTag compoundTag = new CompoundTag(); - compoundTag.put("data", this.save(new CompoundTag())); - NbtUtils.addCurrentDataVersion(compoundTag); - -+ // Paper start -+ Runnable writeRunnable = () -> { - try { - NbtIo.writeCompressed(compoundTag, file); - } catch (IOException var4) { - LOGGER.error("Could not save data {}", this, var4); - } -+ }; -+ -+ if (async) { -+ net.minecraft.Util.ioPool().execute(writeRunnable); -+ } else { -+ writeRunnable.run(); -+ } -+ // Paper end - - this.setDirty(false); - } -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 defe31a5d3aa89a3d18b94f2ff005594e38754b3..14b7d77d1daddf5246a823a6a0fd0f1de159f34e 100644 ---- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java -@@ -118,10 +118,10 @@ public class DimensionDataStorage { - return bl; - } - -- public void save() { -+ public void save(boolean async) { // Paper - this.cache.forEach((id, state) -> { - if (state != null) { -- state.save(this.getDataFile(id)); -+ state.save(this.getDataFile(id), async); // Paper - } - - }); diff --git a/patches/server/1060-Write-SavedData-IO-async.patch b/patches/server/1060-Write-SavedData-IO-async.patch new file mode 100644 index 000000000000..c386e4a82808 --- /dev/null +++ b/patches/server/1060-Write-SavedData-IO-async.patch @@ -0,0 +1,187 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Tue, 27 Jun 2023 11:35:52 -0500 +Subject: [PATCH] Write SavedData IO async + + +diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +index 513833c2ea23df5b079d157bc5cb89d5c9754c0b..ef957036de071908964a53c7a157b95e54dfefe3 100644 +--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java ++++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +@@ -97,6 +97,14 @@ public class ThreadedWorldUpgrader { + } + + this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); ++ // Paper start ++ this.threadPool.execute(() -> { ++ try { ++ worldPersistentData.close(); ++ } catch (IOException ignored) { ++ } ++ }); ++ // Paper end + } + 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 44ada45d9bf2d9b48e5de1c3cb1a855902f3884b..a4410cf67280f8a985e6019e6712c617b31fcd1b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -467,6 +467,12 @@ 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 ++ try { ++ this.dataStorage.close(); ++ } catch (IOException ignored) { ++ } ++ // Paper end + } + + // 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 676087c3addd712939c865b39ddb5d9f0bc7ce25..7564b9226fa38a5d94eba3ca5e579e53892d0706 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1492,7 +1492,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 + } + + this.timings.worldSaveChunks.startTiming(); // Paper +@@ -1528,7 +1528,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); + } + +- this.saveLevelData(); ++ this.saveLevelData(!close); // Paper + if (progressListener != null) { + progressListener.progressStage(Component.translatable("menu.savingChunks")); + } +@@ -1551,12 +1551,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + // CraftBukkit end + } + +- private void saveLevelData() { ++ private void saveLevelData(boolean async) { // Paper + if (this.dragonFight != null) { + this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit + } + +- this.getChunkSource().getDataStorage().save(); ++ this.getChunkSource().getDataStorage().save(async); // Paper + } + + public List getEntities(EntityTypeTest filter, Predicate 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..cf07f435b5ec720807297e47d5f0d515b711fe41 100644 +--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java ++++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +@@ -224,7 +224,12 @@ public class WorldUpgrader { + } + } + +- this.overworldDataStorage.save(); ++ // Paper start ++ try { ++ this.overworldDataStorage.close(); ++ } catch (IOException ignored) { ++ } ++ // Paper end + 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..b3e2954f28986e334089bfff39d92f857bc48a36 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,35 @@ public abstract class SavedData { + return this.dirty; + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper - This is dead + public void save(File file) { ++ save(file, null).join(); // Paper - joining is evil, but we assume the old blocking behavior here just for safety ++ } ++ ++ public java.util.concurrent.CompletableFuture save(File file, @org.jetbrains.annotations.Nullable java.util.concurrent.ExecutorService ioExecutor) { ++ // Paper end + if (this.isDirty()) { + CompoundTag compoundTag = new CompoundTag(); + compoundTag.put("data", this.save(new CompoundTag())); + NbtUtils.addCurrentDataVersion(compoundTag); + ++ // Paper start ++ Runnable writeRunnable = () -> { + try { + NbtIo.writeCompressed(compoundTag, file.toPath()); + } catch (IOException var4) { + LOGGER.error("Could not save data {}", this, var4); + } ++ }; ++ // Paper end + + this.setDirty(false); ++ if (ioExecutor == null) { ++ return java.util.concurrent.CompletableFuture.runAsync(writeRunnable); // Paper - No executor, just use common pool ++ } ++ return java.util.concurrent.CompletableFuture.runAsync(writeRunnable, ioExecutor); // Paper + } ++ return java.util.concurrent.CompletableFuture.completedFuture(null); // Paper + } + + public static record Factory(Supplier constructor, Function 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 d051e8c1db6b5c42b8df0be54d9d48ba0e7b0077..036063e2ba6eeecb2fabe11830370ab41f179f7c 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 + private static final Logger LOGGER = LogUtils.getLogger(); + public final Map cache = Maps.newHashMap(); + private final DataFixer fixerUpper; + private final File dataFolder; ++ protected final java.util.concurrent.ExecutorService ioExecutor; // Paper + + public DimensionDataStorage(File directory, DataFixer dataFixer) { + this.fixerUpper = dataFixer; + this.dataFolder = directory; ++ String worldFolder = dataFolder.getParent(); // Paper ++ this.ioExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("DimensionDataIO - " + worldFolder + " - %d").setDaemon(true).build()); // Paper + } + + private File getDataFile(String id) { +@@ -118,10 +121,23 @@ public class DimensionDataStorage { + return bl; + } + +- public void save() { ++ // Paper start ++ @Override ++ public void close() throws IOException { ++ save(false); ++ this.ioExecutor.shutdown(); ++ } ++ // Paper end ++ ++ public void save(boolean async) { // Paper + this.cache.forEach((id, state) -> { + if (state != null) { +- state.save(this.getDataFile(id)); ++ // Paper start ++ final java.util.concurrent.CompletableFuture save = state.save(this.getDataFile(id), ioExecutor);// Paper ++ if (!async) { ++ save.join(); ++ } ++ // Paper end + } + + });