mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-30 16:19:03 +01:00
fd5c98a9ef
Removes synchronization from sending packets Makes normal packet sends no longer need to be wrapped and queued like it use to work. Adds more packet queue immunities on top of keep alive to let the following scenarios go out without delay: - Keep Alive - Chat - Kick - All of the packets during the Player Joined World event Hoping that latter one helps join timeout issues more too for slow connections. Removes processing packet queue off of main thread - for the few cases where it is allowed, order is not necessary nor should it even be happening concurrently in first place (handshaking/login/status) Ensures packets sent asynchronously are dispatched on main thread This helps ensure safety for ProtocolLib as packet listeners are commonly accessing world state. This will allow you to schedule a packet to be sent async, but itll be dispatched sync for packet listeners to process. This should solve some deadlock risks This may provide a decent performance improvement because thread synchronization incurs a cache reset so by avoiding ever entering a synchronized block, we get to avoid that, and packet sending is a really hot activity.
321 lines
No EOL
15 KiB
Diff
321 lines
No EOL
15 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Shane Freeder <theboyetronic@gmail.com>
|
|
Date: Sun, 9 Jun 2019 03:53:22 +0100
|
|
Subject: [PATCH] incremental chunk saving
|
|
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index 071e5e7f729..48676152152 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -0,0 +0,0 @@ public class PaperWorldConfig {
|
|
keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16);
|
|
log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16));
|
|
}
|
|
+
|
|
+ public int autoSavePeriod = -1;
|
|
+ private void autoSavePeriod() {
|
|
+ autoSavePeriod = getInt("auto-save-interval", -1);
|
|
+ if (autoSavePeriod > 0) {
|
|
+ log("Auto Save Interval: " +autoSavePeriod + " (" + (autoSavePeriod / 20) + "s)");
|
|
+ } else if (autoSavePeriod < 0) {
|
|
+ autoSavePeriod = net.minecraft.server.MinecraftServer.getServer().autosavePeriod;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int maxAutoSaveChunksPerTick = 24;
|
|
+ private void maxAutoSaveChunksPerTick() {
|
|
+ maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
|
|
index 165cb994e84..af0d6aff4de 100644
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
@@ -0,0 +0,0 @@ public class Chunk implements IChunkAccess {
|
|
private TickList<Block> o;
|
|
private TickList<FluidType> p;
|
|
private boolean q;
|
|
- private long lastSaved;
|
|
+ public long lastSaved; // Paper
|
|
private volatile boolean s;
|
|
private long inhabitedTime;
|
|
@Nullable
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index e6d08756f76..6713b7667ae 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
} // Paper - Timings
|
|
}
|
|
|
|
+ // Paper start - duplicate save, but call incremental
|
|
+ public void saveIncrementally() {
|
|
+ this.tickDistanceManager();
|
|
+ try (co.aikar.timings.Timing timed = world.timings.chunkSaveData.startTiming()) { // Paper - Timings
|
|
+ this.playerChunkMap.saveIncrementally();
|
|
+ } // Paper - Timings
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
public void close() throws IOException {
|
|
// CraftBukkit start
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 0ee1d8e4869..7ecf7812631 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
public static int currentTick = 0; // Paper - Further improve tick loop
|
|
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
|
|
public int autosavePeriod;
|
|
+ public boolean serverAutoSave = false; // Paper
|
|
public File bukkitDataPackFolder;
|
|
public CommandDispatcher vanillaCommandDispatcher;
|
|
private boolean forceTicks;
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
this.serverPing.b().a(agameprofile);
|
|
}
|
|
|
|
- if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit
|
|
- MinecraftServer.LOGGER.debug("Autosave started");
|
|
+ //if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // CraftBukkit // Paper - move down
|
|
+ //MinecraftServer.LOGGER.debug("Autosave started"); // Paper
|
|
+ serverAutoSave = (autosavePeriod > 0 && this.ticks % autosavePeriod == 0); // Paper
|
|
this.methodProfiler.enter("save");
|
|
+ if (autosavePeriod > 0 && this.ticks % autosavePeriod == 0) { // Paper
|
|
this.playerList.savePlayers();
|
|
- this.saveChunks(true, false, false);
|
|
+ }// Paper
|
|
+ // Paper start
|
|
+ for (WorldServer world : getWorlds()) {
|
|
+ if (world.paperConfig.autoSavePeriod > 0) {
|
|
+ try {
|
|
+ world.saveIncrementally(serverAutoSave);
|
|
+ } catch (ExceptionWorldConflict exceptionWorldConflict) {
|
|
+ MinecraftServer.LOGGER.warn(exceptionWorldConflict.getMessage());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
this.methodProfiler.exit();
|
|
- MinecraftServer.LOGGER.debug("Autosave finished");
|
|
- }
|
|
+ //MinecraftServer.LOGGER.debug("Autosave finished"); // Paper
|
|
+ //} // Paper
|
|
|
|
this.methodProfiler.enter("snooper");
|
|
if (((DedicatedServer) this).getDedicatedServerProperties().snooperEnabled && !this.snooper.d() && this.ticks > 100) { // Spigot
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
index a640cb3845a..3d255b19647 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
|
|
private final PlayerChunkMap chunkMap; // Paper
|
|
|
|
+ long lastAutoSaveTime; // Paper - incremental autosave
|
|
+ long inactiveTimeStart; // Paper - incremental autosave
|
|
+
|
|
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
|
|
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
|
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
boolean flag2 = playerchunk_state.isAtLeast(PlayerChunk.State.BORDER);
|
|
boolean flag3 = playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER);
|
|
|
|
+ boolean prevHasBeenLoaded = this.hasBeenLoaded; // Paper
|
|
this.hasBeenLoaded |= flag3;
|
|
+ // Paper start - incremental autosave
|
|
+ if (this.hasBeenLoaded & !prevHasBeenLoaded) {
|
|
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
|
|
+ if (timeSinceAutoSave < 0) {
|
|
+ // safest bet is to assume autosave is needed here
|
|
+ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod;
|
|
+ }
|
|
+ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave;
|
|
+ this.chunkMap.autoSaveQueue.add(this);
|
|
+ }
|
|
+ // Paper end
|
|
if (!flag2 && flag3) {
|
|
// Paper start - cache ticking ready status
|
|
int expectCreateCount = ++this.fullChunkCreateCount;
|
|
@@ -0,0 +0,0 @@ public class PlayerChunk {
|
|
}
|
|
|
|
public void m() {
|
|
+ boolean prev = this.hasBeenLoaded; // Paper
|
|
+ this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER);
|
|
+ // Paper start - incremental autosave
|
|
+ if (prev != this.hasBeenLoaded) {
|
|
+ if (this.hasBeenLoaded) {
|
|
+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime;
|
|
+ if (timeSinceAutoSave < 0) {
|
|
+ // safest bet is to assume autosave is needed here
|
|
+ timeSinceAutoSave = this.chunkMap.world.paperConfig.autoSavePeriod;
|
|
+ }
|
|
+ this.lastAutoSaveTime = this.chunkMap.world.getTime() - timeSinceAutoSave;
|
|
+ this.chunkMap.autoSaveQueue.add(this);
|
|
+ } else {
|
|
+ this.inactiveTimeStart = this.chunkMap.world.getTime();
|
|
+ this.chunkMap.autoSaveQueue.remove(this);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+ }
|
|
+
|
|
+ // Paper start - incremental autosave
|
|
+ public boolean setHasBeenLoaded() {
|
|
this.hasBeenLoaded = getChunkState(this.ticketLevel).isAtLeast(PlayerChunk.State.BORDER);
|
|
+ return this.hasBeenLoaded;
|
|
}
|
|
+ // Paper end
|
|
|
|
public void a(ProtoChunkExtension protochunkextension) {
|
|
for (int i = 0; i < this.statusFutures.length(); ++i) {
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index 34f470779fa..4f5b5161448 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
}
|
|
|
|
+ // Paper start - incremental autosave
|
|
+ final it.unimi.dsi.fastutil.objects.ObjectRBTreeSet<PlayerChunk> autoSaveQueue = new it.unimi.dsi.fastutil.objects.ObjectRBTreeSet<>((playerchunk1, playerchunk2) -> {
|
|
+ int timeCompare = Long.compare(playerchunk1.lastAutoSaveTime, playerchunk2.lastAutoSaveTime);
|
|
+ if (timeCompare != 0) {
|
|
+ return timeCompare;
|
|
+ }
|
|
+
|
|
+ return Long.compare(MCUtil.getCoordinateKey(playerchunk1.location), MCUtil.getCoordinateKey(playerchunk2.location));
|
|
+ });
|
|
+
|
|
+ protected void saveIncrementally() {
|
|
+ int savedThisTick = 0;
|
|
+ // optimized since we search far less chunks to hit ones that need to be saved
|
|
+ List<PlayerChunk> reschedule = new ArrayList<>(this.world.paperConfig.maxAutoSaveChunksPerTick);
|
|
+ long currentTick = this.world.getTime();
|
|
+ long maxSaveTime = currentTick - this.world.paperConfig.autoSavePeriod;
|
|
+
|
|
+ for (Iterator<PlayerChunk> iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) {
|
|
+ PlayerChunk playerchunk = iterator.next();
|
|
+ if (playerchunk.lastAutoSaveTime > maxSaveTime) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ iterator.remove();
|
|
+
|
|
+ IChunkAccess ichunkaccess = playerchunk.getChunkSave().getNow(null);
|
|
+ if (ichunkaccess instanceof Chunk) {
|
|
+ boolean shouldSave = ((Chunk)ichunkaccess).lastSaved <= maxSaveTime;
|
|
+
|
|
+ if (shouldSave && this.saveChunk(ichunkaccess)) {
|
|
+ ++savedThisTick;
|
|
+
|
|
+ if (!playerchunk.setHasBeenLoaded()) {
|
|
+ // do not fall through to reschedule logic
|
|
+ playerchunk.inactiveTimeStart = currentTick;
|
|
+ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) {
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ reschedule.add(playerchunk);
|
|
+
|
|
+ if (savedThisTick >= this.world.paperConfig.maxAutoSaveChunksPerTick) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (int i = 0, len = reschedule.size(); i < len; ++i) {
|
|
+ PlayerChunk playerchunk = reschedule.get(i);
|
|
+ playerchunk.lastAutoSaveTime = this.world.getTime();
|
|
+ this.autoSaveQueue.add(playerchunk);
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
protected void save(boolean flag) {
|
|
if (flag) {
|
|
List<PlayerChunk> list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList());
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
this.world.unloadChunk(chunk);
|
|
}
|
|
+ this.autoSaveQueue.remove(playerchunk); // Paper
|
|
|
|
this.lightEngine.a(ichunkaccess.getPos());
|
|
this.lightEngine.queueUpdate();
|
|
@@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
playerchunk.a(new ProtoChunkExtension(chunk));
|
|
}
|
|
|
|
+ chunk.setLastSaved(this.world.getTime() - 1); // Paper - avoid autosaving newly generated/loaded chunks
|
|
+
|
|
chunk.a(() -> {
|
|
return PlayerChunk.getChunkState(playerchunk.getTicketLevel());
|
|
});
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index ce506e0e129..ad5e538b249 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -0,0 +0,0 @@ public class WorldServer extends World {
|
|
return this.worldProvider.c();
|
|
}
|
|
|
|
+ // Paper start - derived from below
|
|
+ public void saveIncrementally(boolean doFull) throws ExceptionWorldConflict {
|
|
+ ChunkProviderServer chunkproviderserver = this.getChunkProvider();
|
|
+
|
|
+ if (doFull) {
|
|
+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
|
|
+ }
|
|
+
|
|
+ try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) {
|
|
+ if (doFull) {
|
|
+ this.saveData();
|
|
+ }
|
|
+
|
|
+ timings.worldSaveChunks.startTiming(); // Paper
|
|
+ if (!this.isSavingDisabled()) chunkproviderserver.saveIncrementally();
|
|
+ timings.worldSaveChunks.stopTiming(); // Paper
|
|
+
|
|
+
|
|
+ // CraftBukkit start - moved from MinecraftServer.saveChunks
|
|
+ // PAIL - rename
|
|
+ if (doFull) {
|
|
+ WorldServer worldserver1 = this;
|
|
+ WorldData worlddata = worldserver1.getWorldData();
|
|
+
|
|
+ worldserver1.getWorldBorder().save(worlddata);
|
|
+ worlddata.setCustomBossEvents(this.server.getBossBattleCustomData().save());
|
|
+ worldserver1.getDataManager().saveWorldData(worlddata, this.server.getPlayerList().save());
|
|
+ // CraftBukkit end
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public void save(@Nullable IProgressUpdate iprogressupdate, boolean flag, boolean flag1) throws ExceptionWorldConflict {
|
|
ChunkProviderServer chunkproviderserver = this.getChunkProvider();
|
|
|
|
if (!flag1) {
|
|
- org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
|
|
+ if (flag) org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
|
|
try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
|
|
if (iprogressupdate != null) {
|
|
iprogressupdate.a(new ChatMessage("menu.savingLevel", new Object[0]));
|
|
@@ -0,0 +0,0 @@ public class WorldServer extends World {
|
|
// CraftBukkit end
|
|
}
|
|
|
|
+ protected void saveData() throws ExceptionWorldConflict { this.m_(); } // Paper - OBFHELPER
|
|
protected void m_() throws ExceptionWorldConflict {
|
|
this.checkSession();
|
|
this.worldProvider.i();
|
|
--
|