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. * 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 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 new file mode 100644
index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7ad6c81edf index 0000000000000000000000000000000000000000..261b3019878c31a9e44e56b6611899de6c00ebee
--- /dev/null --- /dev/null
+++ b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java +++ b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java
@@ -0,0 +1,174 @@ @@ -0,0 +1,226 @@
+package io.papermc.paper.threadedregions; +package io.papermc.paper.threadedregions;
+ +
+import ca.spottedleaf.moonrise.common.util.WorldUtil;
+import com.mojang.logging.LogUtils; +import com.mojang.logging.LogUtils;
+import net.minecraft.server.MinecraftServer; +import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ChunkPos;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.slf4j.Logger; +import org.slf4j.Logger;
+import java.util.ArrayList; +import java.util.ArrayList;
+import java.util.List; +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 { + 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) { + } 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 + @Override
+ public final void run() { + public final void run() {
+ // await scheduler termination + // 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))) { + if (TickRegions.getScheduler().halt(true, TimeUnit.SECONDS.toNanos(60L))) {
+ LOGGER.info("Scheduler halted"); + LOGGER.info("Scheduler halted");
+ } else { + } else {
@ -1335,7 +1363,7 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a
+ +
+ MinecraftServer.getServer().stopServer(); // stop part 1: most logic, kicking players, plugins, etc + 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 + // 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()) { + for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ try { + try {
+ world.moonrise$getChunkTaskScheduler().halt(false, 0L); + world.moonrise$getChunkTaskScheduler().halt(false, 0L);
@ -1347,27 +1375,51 @@ index 0000000000000000000000000000000000000000..e8550cd33ca171aeffb1f40854f80c7a
+ this.haltChunkSystem(world); + this.haltChunkSystem(world);
+ } + }
+ LOGGER.info("Halted chunk systems"); + LOGGER.info("Halted chunk systems");
+
+ LOGGER.info("Finishing pending teleports...");
+ for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) { + for (final ServerLevel world : MinecraftServer.getServer().getAllLevels()) {
+ final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>> + final List<ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData>>
+ regions = new ArrayList<>(); + regions = new ArrayList<>();
+ world.regioniser.computeForAllRegionsUnsynchronised(regions::add); + world.regioniser.computeForAllRegionsUnsynchronised(regions::add);
+ +
+ for (int i = 0, len = regions.size(); i < len; ++i) { + for (int i = 0, len = regions.size(); i < len; ++i) {
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = regions.get(i); + this.finishTeleportations(regions.get(i), world);
+ this.finishTeleportations(region, world);
+ } + }
+
+ 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.saveLevelData(world);
+ } + }
+ // moved from stop part 1 + LOGGER.info("Finished pending teleports");
+ // 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("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) {
+ 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) + "'");
+ }
+ LOGGER.info("Saved all worlds");
+
+ // Note: only save after world data and pending teleportations
+ LOGGER.info("Saving all player data...");
+ MinecraftServer.getServer().getPlayerList().saveAll(); + MinecraftServer.getServer().getPlayerList().saveAll();
+ LOGGER.info("Saved all player data");
+ +
+ MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc) + MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc)
+ // done, part 2 should call exit() + // done, part 2 should call exit()