Skip to content

Commit

Permalink
Ensure that we can track saves
Browse files Browse the repository at this point in the history
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
  • Loading branch information
electronicboy committed Jan 4, 2024
1 parent 610eac1 commit 4e672d5
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 109 deletions.
109 changes: 0 additions & 109 deletions patches/server/0982-Write-SavedData-IO-async.patch

This file was deleted.

187 changes: 187 additions & 0 deletions patches/server/1060-Write-SavedData-IO-async.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cryptite <[email protected]>
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 <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..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<Void> 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<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..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<String, SavedData> 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<Void> save = state.save(this.getDataFile(id), ioExecutor);// Paper
+ if (!async) {
+ save.join();
+ }
+ // Paper end
}

});

0 comments on commit 4e672d5

Please sign in to comment.