Skip to content

Commit

Permalink
Adjust shutdown logic to close player inventories
Browse files Browse the repository at this point in the history
We need to drop items in special containers on shutdown
to prevent them from being lost.

Fixes #148
  • Loading branch information
Spottedleaf committed Dec 3, 2024
1 parent 815dd7b commit eb5ec0b
Showing 1 changed file with 66 additions and 14 deletions.
80 changes: 66 additions & 14 deletions patches/server/0003-Threaded-Regions.patch
Original file line number Diff line number Diff line change
Expand Up @@ -1195,17 +1195,21 @@ index c03608fec96b51e1867f43d8f42e5aefb1520e46..127d96280cad2d4e5db574a089d67ad6
* on all currently scheduled tasks.
diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7ad6c81edf
index 0000000000000000000000000000000000000000..261b3019878c31a9e44e56b6611899de6c00ebee
--- /dev/null
+++ b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java
@@ -0,0 +1,174 @@
@@ -0,0 +1,226 @@
+package io.papermc.paper.threadedregions;
+
+import ca.spottedleaf.moonrise.common.util.WorldUtil;
+import com.mojang.logging.LogUtils;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.ChunkPos;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.slf4j.Logger;
+import java.util.ArrayList;
+import java.util.List;
Expand Down Expand Up @@ -1314,18 +1318,42 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a
+ }
+ }
+
+ private void haltWorldNoRegions(final ServerLevel world) {
+ private void closePlayerInventories(final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region) {
+ ChunkPos center = null;
+ try {
+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true, true, true, false);
+ this.shuttingDown = region;
+ center = region.getCenterChunk();
+
+ final RegionizedWorldData worldData = region.regioniser.world.worldRegionData.get();
+
+ for (final ServerPlayer player : worldData.getLocalPlayers()) {
+ try {
+ // close inventory
+ if (player.containerMenu != player.inventoryMenu) {
+ player.closeContainer(InventoryCloseEvent.Reason.DISCONNECT);
+ }
+
+ // drop carried item
+ if (!player.containerMenu.getCarried().isEmpty()) {
+ ItemStack carried = player.containerMenu.getCarried();
+ player.containerMenu.setCarried(ItemStack.EMPTY);
+ player.drop(carried, false);
+ }
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to close player inventory for player: " + player, thr);
+ }
+ }
+ } catch (final Throwable thr) {
+ LOGGER.error("Failed to close world '" + world.getWorld().getName() + "' with no regions", thr);
+ LOGGER.error("Failed to close player inventories for region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'", thr);
+ } finally {
+ this.shuttingDown = null;
+ }
+ }
+
+ @Override
+ public final void run() {
+ // await scheduler termination
+ LOGGER.info("Awaiting scheduler termination for 60s");
+ LOGGER.info("Awaiting scheduler termination for 60s...");
+ if (TickRegions.getScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) {
+ LOGGER.info("Scheduler halted");
+ } else {
Expand All @@ -1335,7 +1363,7 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a
+
+ MinecraftServer.getServer().stopServer(); // stop part 1: most logic, kicking players, plugins, etc
+ // halt all chunk systems first so that any in-progress chunk generation stops
+ LOGGER.info("Halting chunk systems");
+ LOGGER.info("Halting chunk systems...");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ try {
+ world.moonrise$getChunkTaskScheduler().halt(false, 0L);
Expand All @@ -1347,27 +1375,51 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a
+ this.haltChunkSystem(world);
+ }
+ LOGGER.info("Halted chunk systems");
+
+ LOGGER.info("Finishing pending teleports...");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>>
+ regions = new ArrayList<>();
+ world.regioniser.computeForAllRegionsUnsynchronised(regions::add);
+
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = regions.get(i);
+ this.finishTeleportations(region, world);
+ this.finishTeleportations(regions.get(i), world);
+ }
+ }
+ LOGGER.info("Finished pending teleports");
+
+ LOGGER.info("Saving all worlds");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ LOGGER.info("Saving world data for world '" + WorldUtil.getWorldName(world) + "'");
+
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>>
+ regions = new ArrayList<>();
+ world.regioniser.computeForAllRegionsUnsynchronised(regions::add);
+
+ LOGGER.info("Closing player inventories...");
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ this.closePlayerInventories(regions.get(i));
+ }
+ LOGGER.info("Closed player inventories");
+
+ LOGGER.info("Saving chunks...");
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = regions.get(i);
+ this.saveRegionChunks(region, (i + 1) == len);
+ this.saveRegionChunks(regions.get(i), (i + 1) == len);
+ }
+ LOGGER.info("Saved chunks");
+
+ LOGGER.info("Saving level data...");
+ this.saveLevelData(world);
+ LOGGER.info("Saved level data");
+
+ LOGGER.info("Saved world data for world '" + WorldUtil.getWorldName(world) + "'");
+ }
+ // moved from stop part 1
+ // we need this to be after saving level data, as that will complete any teleportations the player is in
+ LOGGER.info("Saving players");
+ LOGGER.info("Saved all worlds");
+
+ // Note: only save after world data and pending teleportations
+ LOGGER.info("Saving all player data...");
+ MinecraftServer.getServer().getPlayerList().saveAll();
+ LOGGER.info("Saved all player data");
+
+ MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc)
+ // done, part 2 should call exit()
Expand Down

0 comments on commit eb5ec0b

Please sign in to comment.