From 86924053639d54ca2a83cafc327a10c43268ec7f Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Thu, 1 Jun 2023 18:17:48 -0400 Subject: [PATCH] Wait for thread executors to shut down --- .../java/emu/grasscutter/Grasscutter.java | 33 +++++++++++++---- .../grasscutter/database/DatabaseHelper.java | 14 +++---- .../grasscutter/server/game/GameServer.java | 37 +++++++++++++------ .../server/game/GameSessionManager.java | 10 ++--- .../java/emu/grasscutter/utils/Utils.java | 16 ++++++-- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index de66a9a50..bf5a15b9c 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,14 +1,11 @@ package emu.grasscutter; -import static emu.grasscutter.config.Configuration.SERVER; -import static emu.grasscutter.utils.lang.Language.translate; - import ch.qos.logback.classic.*; import emu.grasscutter.auth.*; import emu.grasscutter.command.*; import emu.grasscutter.config.ConfigContainer; import emu.grasscutter.data.ResourceLoader; -import emu.grasscutter.database.DatabaseManager; +import emu.grasscutter.database.*; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHelper; import emu.grasscutter.server.dispatch.DispatchServer; @@ -21,16 +18,20 @@ import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.*; import emu.grasscutter.utils.lang.Language; import io.netty.util.concurrent.FastThreadLocalThread; -import java.io.*; -import java.util.Calendar; -import java.util.concurrent.*; -import javax.annotation.Nullable; import lombok.*; import org.jline.reader.*; import org.jline.terminal.*; import org.reflections.Reflections; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; +import java.io.*; +import java.util.Calendar; +import java.util.concurrent.*; + +import static emu.grasscutter.config.Configuration.SERVER; +import static emu.grasscutter.utils.lang.Language.translate; + public final class Grasscutter { public static final File configFile = new File("./config.json"); public static final Reflections reflector = new Reflections("emu.grasscutter"); @@ -183,6 +184,22 @@ public final class Grasscutter { private static void onShutdown() { // Disable all plugins. if (pluginManager != null) pluginManager.disablePlugins(); + + try { + // Wait for Grasscutter's thread pool to finish. + var executor = Grasscutter.getThreadPool(); + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + + // Wait for database operations to finish. + var dbExecutor = DatabaseHelper.getEventExecutor(); + dbExecutor.shutdown(); + if (!dbExecutor.awaitTermination(5, TimeUnit.SECONDS)) { + dbExecutor.shutdownNow(); + } + } catch (InterruptedException ignored) { } } /* diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index f4079bd56..6dbda15ac 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -1,12 +1,8 @@ package emu.grasscutter.database; -import static com.mongodb.client.model.Filters.eq; - -import dev.morphia.query.FindOptions; -import dev.morphia.query.Sort; +import dev.morphia.query.*; import dev.morphia.query.experimental.filters.Filters; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.*; import emu.grasscutter.game.Account; import emu.grasscutter.game.achievement.Achievements; import emu.grasscutter.game.activity.PlayerActivityData; @@ -23,12 +19,16 @@ import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.world.SceneGroupInstance; import emu.grasscutter.utils.objects.Returnable; import io.netty.util.concurrent.FastThreadLocalThread; +import lombok.Getter; + import java.util.List; import java.util.concurrent.*; import java.util.stream.Stream; +import static com.mongodb.client.model.Filters.eq; + public final class DatabaseHelper { - private static final ExecutorService eventExecutor = + @Getter private static final ExecutorService eventExecutor = new ThreadPoolExecutor( 6, 6, diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 485704ba7..7bb95b1d5 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -1,8 +1,5 @@ package emu.grasscutter.server.game; -import static emu.grasscutter.config.Configuration.*; -import static emu.grasscutter.utils.lang.Language.translate; - import emu.grasscutter.*; import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.database.DatabaseHelper; @@ -32,14 +29,19 @@ import emu.grasscutter.server.event.internal.*; import emu.grasscutter.server.event.types.ServerEvent; import emu.grasscutter.server.scheduler.ServerTaskScheduler; import emu.grasscutter.task.TaskMap; -import java.net.*; -import java.time.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import emu.grasscutter.utils.Utils; import kcp.highway.*; import lombok.*; import org.jetbrains.annotations.NotNull; +import java.net.*; +import java.time.*; +import java.util.*; +import java.util.concurrent.*; + +import static emu.grasscutter.config.Configuration.*; +import static emu.grasscutter.utils.lang.Language.translate; + @Getter public final class GameServer extends KcpServer implements Iterable { // Game server base @@ -326,16 +328,27 @@ public final class GameServer extends KcpServer implements Iterable { } public void onServerShutdown() { - ServerStopEvent event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); + var event = new ServerStopEvent(ServerEvent.Type.GAME, OffsetDateTime.now()); event.call(); this.getPlayers() - .forEach( - (uid, player) -> { - player.getSession().close(); - }); + .forEach( + (uid, player) -> player.getSession().close()); this.getWorlds().forEach(World::save); + + Utils.sleep(1000L); // Wait 1 second for operations to finish. + + try { + var threadPool = GameSessionManager.getLogicThread(); + + // Shutdown network thread. + threadPool.shutdownGracefully(); + // Wait for the network thread to finish. + if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) { + Grasscutter.getLogger().error("Logic thread did not terminate!"); + } + } catch (InterruptedException ignored) { } } @NotNull @Override diff --git a/src/main/java/emu/grasscutter/server/game/GameSessionManager.java b/src/main/java/emu/grasscutter/server/game/GameSessionManager.java index 178e1a440..d529f7579 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSessionManager.java +++ b/src/main/java/emu/grasscutter/server/game/GameSessionManager.java @@ -2,16 +2,16 @@ package emu.grasscutter.server.game; import emu.grasscutter.Grasscutter; import emu.grasscutter.utils.Utils; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import io.netty.buffer.*; import io.netty.channel.DefaultEventLoop; +import kcp.highway.*; +import lombok.Getter; + import java.net.InetSocketAddress; import java.util.concurrent.ConcurrentHashMap; -import kcp.highway.KcpListener; -import kcp.highway.Ukcp; public class GameSessionManager { - private static final DefaultEventLoop logicThread = new DefaultEventLoop(); + @Getter private static final DefaultEventLoop logicThread = new DefaultEventLoop(); private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); private static final KcpListener listener = new KcpListener() { diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index c063b5b32..ad2f0244a 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -504,9 +504,7 @@ public final class Utils { * @return A list of all fields in the class. */ public static List getAllFields(Class type) { - var fields = new LinkedList<>( - Arrays.asList(type.getDeclaredFields()) - ); + var fields = new LinkedList<>(Arrays.asList(type.getDeclaredFields())); // Check for superclasses. if (type.getSuperclass() != null) { @@ -515,4 +513,16 @@ public final class Utils { return fields; } + + /** + * Sleeps the current thread without an exception. + * + * @param millis The amount of milliseconds to sleep. + */ + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ignored) { + } + } }