Adjust shutdown logic to close player inventories

We need to drop items in special containers on shutdown
to prevent them from being lost.

Fixes https://github.com/PaperMC/Folia/issues/148
This commit is contained in:
Spottedleaf 2024-12-02 23:52:38 -08:00
parent 815dd7bae8
commit eb5ec0b932

View File

@ -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;
@ -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 world '" + world.getWorld().getName() + "' with no regions", thr);
+ LOGGER.error("Failed to close player inventory for player: " + player, thr);
+ }
+ }
+ } catch (final Throwable 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 {
@ -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);
@ -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) {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = regions.get(i);
+ this.saveRegionChunks(region, (i + 1) == len);
+ this.closePlayerInventories(regions.get(i));
+ }
+ LOGGER.info("Closed player inventories");
+
+ this.saveLevelData(world);
+ LOGGER.info("Saving chunks...");
+ for (int i = 0, len = regions.size(); i < len; ++i) {
+ this.saveRegionChunks(regions.get(i), (i + 1) == len);
+ }
+ // 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 chunks");
+
+ LOGGER.info("Saving level data...");
+ this.saveLevelData(world);
+ LOGGER.info("Saved level data");
+
+ LOGGER.info("Saved world data for world '" + WorldUtil.getWorldName(world) + "'");
+ }
+ 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()