mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-01 00:10:32 +01:00
89d51d5f29
Because this exploit has been widely known for years and has not been fixed by Mojang, we decided that it was worth allowing people to toggle it on/off due to how easy it is to make it configurable. It should be noted that this decision does not promise all future exploits will be configurable.
191 lines
9.6 KiB
Diff
191 lines
9.6 KiB
Diff
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 d051e8c1db6b5c42b8df0be54d9d48ba0e7b0077..1c16f43872d9cf9b087f247e9aaa04e7abe3d4ae 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
|
|
}
|
|
|
|
});
|