From f0e21cae9c66db7634d462c0d6426a808da0396d Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 20 Jun 2026 11:47:23 +1000 Subject: [PATCH] Fix 26.2+ clients hanging on shutdown Move Session Manager to PlatformManager and shut it down with unready event. Fix SessionManager#unload canceling timer, which prevents automatic saving of sessions after quitting a singleplayer world once. --- .../worldedit/bukkit/WorldEditPlugin.java | 1 - .../com/sk89q/worldedit/cli/CLIWorldEdit.java | 3 +- .../worldedit/coremc/internal/CoreMcMod.java | 2 -- .../internal/util/DocumentationPrinter.kt | 7 ++-- .../java/com/sk89q/worldedit/WorldEdit.java | 3 +- .../extension/platform/PlatformManager.java | 21 ++++++++++++ .../worldedit/session/SessionManager.java | 32 ++++++++++++------- .../worldedit/sponge/SpongeWorldEdit.java | 1 - 8 files changed, 49 insertions(+), 21 deletions(-) diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index fddc7a1b8b..92d6b3d338 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -307,7 +307,6 @@ private void loadAdapter() { @Override public void onDisable() { WorldEdit worldEdit = WorldEdit.getInstance(); - worldEdit.getSessionManager().unload(); if (platform != null) { worldEdit.getEventBus().post(new PlatformUnreadyEvent(platform)); worldEdit.getPlatformManager().unregister(platform); diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIWorldEdit.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIWorldEdit.java index b561d1c63d..29e7963aff 100644 --- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIWorldEdit.java +++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIWorldEdit.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent; import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -212,7 +213,7 @@ public void onStarted() { public void onStopped() { WorldEdit worldEdit = WorldEdit.getInstance(); - worldEdit.getSessionManager().unload(); + worldEdit.getEventBus().post(new PlatformUnreadyEvent(platform)); worldEdit.getPlatformManager().unregister(platform); } diff --git a/worldedit-core-mc/src/main/java/com/sk89q/worldedit/coremc/internal/CoreMcMod.java b/worldedit-core-mc/src/main/java/com/sk89q/worldedit/coremc/internal/CoreMcMod.java index e86bd55427..db728ca770 100644 --- a/worldedit-core-mc/src/main/java/com/sk89q/worldedit/coremc/internal/CoreMcMod.java +++ b/worldedit-core-mc/src/main/java/com/sk89q/worldedit/coremc/internal/CoreMcMod.java @@ -245,8 +245,6 @@ protected void serverStarted(MinecraftServer server) { } protected void serverStopping() { - WorldEdit worldEdit = WorldEdit.getInstance(); - worldEdit.getSessionManager().unload(); WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); } diff --git a/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/DocumentationPrinter.kt b/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/DocumentationPrinter.kt index 15418a7782..dc0a75942a 100644 --- a/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/DocumentationPrinter.kt +++ b/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/DocumentationPrinter.kt @@ -37,6 +37,7 @@ import com.sk89q.worldedit.command.ToolUtilCommands import com.sk89q.worldedit.command.UtilityCommands import com.sk89q.worldedit.command.util.PermissionCondition import com.sk89q.worldedit.event.platform.PlatformReadyEvent +import com.sk89q.worldedit.event.platform.PlatformUnreadyEvent import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent import com.sk89q.worldedit.internal.command.CommandUtil import com.sk89q.worldedit.util.formatting.text.TextComponent @@ -337,9 +338,9 @@ Other Permissions */ @JvmStatic fun main(args: Array) { + val plat = DocumentationPlatform() + WorldEdit.getInstance().platformManager.register(plat) try { - val plat = DocumentationPlatform() - WorldEdit.getInstance().platformManager.register(plat) WorldEdit.getInstance().eventBus.post(PlatformReadyEvent(plat)) WorldEdit.getInstance().eventBus.post(PlatformsRegisteredEvent()) val printer = DocumentationPrinter() @@ -348,7 +349,7 @@ Other Permissions writeOutput("commands.rst", printer.cmdOutput.toString()) writeOutput("permissions.rst", printer.permsOutput.toString()) } finally { - WorldEdit.getInstance().sessionManager.unload() + WorldEdit.getInstance().eventBus.post(PlatformUnreadyEvent(plat)) } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java index 12896bf333..f2d76b0a2c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -120,7 +120,6 @@ public final class WorldEdit { private final PlatformManager platformManager = new PlatformManager(this); @Deprecated private final EditSessionFactory editSessionFactory = new EditSessionFactory.EditSessionFactoryImpl(); - private final SessionManager sessions = new SessionManager(this); private final Supervisor supervisor = new SimpleSupervisor(); private final AssetLoaders assetLoaders = new AssetLoaders(this); private final SchematicsManager schematicsManager = new SchematicsManager(this); @@ -237,7 +236,7 @@ public PatternFactory getPatternFactory() { * @return the session manager */ public SessionManager getSessionManager() { - return sessions; + return platformManager.getSessionManager(); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index eec10bd4dd..76e0f815fc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -41,6 +41,7 @@ import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.event.platform.PlayerInputEvent; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.session.SessionManager; import com.sk89q.worldedit.session.request.Request; import com.sk89q.worldedit.util.HandSide; import com.sk89q.worldedit.util.Location; @@ -79,6 +80,7 @@ public class PlatformManager { private final WorldEdit worldEdit; private final PlatformCommandManager platformCommandManager; private final SimpleLifecycled executorService; + private final SimpleLifecycled sessionManager; private final Map platforms = Maps.newHashMap(); private final ImmutableMap> preferences = Stream.of(Capability.values()) .collect(Maps.toImmutableEnumMap( @@ -100,6 +102,7 @@ public PlatformManager(WorldEdit worldEdit) { this.worldEdit = worldEdit; this.platformCommandManager = new PlatformCommandManager(worldEdit, this); this.executorService = SimpleLifecycled.invalid(); + this.sessionManager = SimpleLifecycled.invalid(); // Register this instance for events worldEdit.getEventBus().register(this); @@ -342,6 +345,19 @@ private static ListeningExecutorService createExecutor() { 0, 1, 20, "WorldEdit Task Executor - %s")); } + /** + * Get the session manager. + * + * @return the session manager + */ + public SessionManager getSessionManager() { + return sessionManager.valueOrThrow(); + } + + private static SessionManager createSessionManager() { + return new SessionManager(WorldEdit.getInstance()); + } + /** * Get the current configuration. * @@ -406,6 +422,9 @@ public void handleNewPlatformReady(PlatformReadyEvent event) { if (!executorService.isValid()) { executorService.newValue(createExecutor()); } + if (!sessionManager.isValid()) { + sessionManager.newValue(createSessionManager()); + } } /** @@ -420,6 +439,8 @@ public void handleNewPlatformUnready(PlatformUnreadyEvent event) { if (!platforms.containsValue(true)) { executorService.value().ifPresent(ListeningExecutorService::shutdownNow); executorService.invalidate(); + sessionManager.value().ifPresent(SessionManager::unload); + sessionManager.invalidate(); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java index 2131f5dd87..89b64e1752 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -36,7 +36,6 @@ import com.sk89q.worldedit.session.storage.JsonFileSessionStore; import com.sk89q.worldedit.session.storage.SessionStore; import com.sk89q.worldedit.session.storage.VoidStore; -import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors; import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.world.gamemode.GameModes; import com.sk89q.worldedit.world.item.ItemType; @@ -50,12 +49,13 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.Timer; import java.util.TimerTask; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; @@ -71,13 +71,13 @@ public class SessionManager { public static int EXPIRATION_GRACE = 10 * 60 * 1000; private static final int FLUSH_PERIOD = 1000 * 30; - private static final ExecutorService executorService = EvenMoreExecutors.newBoundedCachedThreadPool( - 0, 1, 5, "WorldEdit Session Saver - %s" - ); private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Set warnedInvalidTool = Collections.newSetFromMap(new ConcurrentHashMap<>()); - private final Timer timer = new Timer("WorldEdit Session Manager"); + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool( + 1, + Thread.ofVirtual().name("WorldEdit Session Manager").factory() + ); private final WorldEdit worldEdit; private final Map sessions = new HashMap<>(); private SessionStore store = new VoidStore(); @@ -93,7 +93,7 @@ public SessionManager(WorldEdit worldEdit) { this.worldEdit = worldEdit; worldEdit.getEventBus().register(this); - timer.schedule(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD); + var _ = executorService.scheduleAtFixedRate(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD, TimeUnit.MILLISECONDS); } /** @@ -307,11 +307,21 @@ public synchronized void remove(SessionOwner owner) { } /** - * Called to unload this session manager. + * Called to unload this session manager permanently. */ - public synchronized void unload() { + public void unload() { clear(); - timer.cancel(); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + var tasks = executorService.shutdownNow(); + WorldEdit.logger.error("Session manager is taking too long to exit. List of active tasks: ({})\n{}", + tasks.size(), + tasks.stream().map(run -> run.getClass() + ": " + run + "\n")); + } + } catch (InterruptedException e) { + WorldEdit.logger.error("Interrupted while waiting for session manager to shutdown", e); + } } /** diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java index af9afa2e9a..021f3817ca 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java @@ -253,7 +253,6 @@ public void serverStarted(StartedEngineEvent event) { @Listener public void serverStopping(StoppingEngineEvent event) { WorldEdit worldEdit = WorldEdit.getInstance(); - worldEdit.getSessionManager().unload(); WorldEdit.getInstance().getEventBus().post(new PlatformUnreadyEvent(platform)); }