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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -0,0 +0,0 @@ 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 @@ -0,0 +0,0 @@ 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")); } @@ -0,0 +0,0 @@ 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 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/saveddata/SavedData.java +++ b/src/main/java/net/minecraft/world/level/saveddata/SavedData.java @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java @@ -0,0 +0,0 @@ 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) { @@ -0,0 +0,0 @@ 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 } }); }