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 Co-Authored-By: Shane Freeder diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index cdb9160244cc69acd36ac9afcfe109a15ab2ab58..8a0b00d645e4cf2ca96ec7e8ebc6ef726a0ab8b0 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -369,6 +369,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 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 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 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( 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 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 save = state.save(this.getDataFile(id), this.registries, this.ioExecutor); + if (!async) { + save.join(); + } + // Paper end - Write SavedData IO async } }); }