From af97c1b4d5dbe23792cceb7967c513c9a672cd6c Mon Sep 17 00:00:00 2001 From: Ty Conner <tyconner@itcproductions.com> Date: Sun, 12 Apr 2020 18:54:32 -0400 Subject: [PATCH] Disallow [re]entry into world if shutdown is in progress (#2794) * Disallow [re]entry into world if shutdown is in progress * Update Session.cs * Update Session.cs * Update ServerManager.cs * session management * Update ServerManager.cs * Update Session.cs --- Source/ACE.Server/Managers/ServerManager.cs | 67 ++++++++++++++++--- .../Network/Enum/SessionTerminationReason.cs | 2 + .../Network/Handlers/CharacterHandler.cs | 30 +++++++++ .../Network/Managers/NetworkManager.cs | 13 ++++ Source/ACE.Server/Network/Session.cs | 15 +++-- 5 files changed, 111 insertions(+), 16 deletions(-) diff --git a/Source/ACE.Server/Managers/ServerManager.cs b/Source/ACE.Server/Managers/ServerManager.cs index 4c8ceca6fb..c49f587839 100644 --- a/Source/ACE.Server/Managers/ServerManager.cs +++ b/Source/ACE.Server/Managers/ServerManager.cs @@ -7,6 +7,7 @@ using ACE.Database; using ACE.Entity.Enum; using ACE.Server.Network.GameMessages.Messages; +using ACE.Server.Network.Managers; namespace ACE.Server.Managers { @@ -29,6 +30,11 @@ public static class ServerManager /// </summary> public static bool ShutdownInitiated { get; private set; } + /// <summary> + /// Indicates server shutting down. + /// </summary> + public static bool ShutdownInProgress { get; private set; } + /// <summary> /// The amount of seconds that the server will wait before unloading the application. /// </summary> @@ -116,6 +122,8 @@ private static void ShutdownServer() Thread.Sleep(10); } + ShutdownInProgress = true; + PropertyManager.ResyncVariables(); PropertyManager.StopUpdating(); @@ -125,11 +133,28 @@ private static void ShutdownServer() foreach (var player in PlayerManager.GetAllOnline()) player.Session.LogOffPlayer(true); - log.Info("Waiting for all players to log off..."); + // Wait for all players to log out + var logUpdateTS = DateTime.MinValue; + int playerCount; + while ((playerCount = PlayerManager.GetOnlineCount()) > 0) + { + logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {playerCount} player{(playerCount > 1 ? "s" : "")} to log off..."); + Thread.Sleep(10); + } - // wait 10 seconds for log-off - while (PlayerManager.GetOnlineCount() > 0) + log.Debug("Disconnecting all sessions..."); + + // disconnect each session + NetworkManager.DisconnectAllSessionsForShutdown(); + + // Wait for all sessions to drop out + logUpdateTS = DateTime.MinValue; + int sessionCount; + while ((sessionCount = NetworkManager.GetSessionCount()) > 0) + { + logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {sessionCount} session{(sessionCount > 1 ? "s" : "")} to disconnect..."); Thread.Sleep(10); + } log.Debug("Adding all landblocks to destruction queue..."); @@ -137,30 +162,39 @@ private static void ShutdownServer() // The actual unloading will happen in WorldManager.UpdateGameWorld LandblockManager.AddAllActiveLandblocksToDestructionQueue(); - log.Info("Waiting for all active landblocks to unload..."); - - while (LandblockManager.GetLoadedLandblocks().Count > 0) + // Wait for all landblocks to unload + logUpdateTS = DateTime.MinValue; + int landblockCount; + while ((landblockCount = LandblockManager.GetLoadedLandblocks().Count) > 0) + { + logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for {landblockCount} loaded landblock{(landblockCount > 1 ? "s" : "")} to unload..."); Thread.Sleep(10); + } log.Debug("Stopping world..."); // Disabled thread update loop WorldManager.StopWorld(); - log.Info("Waiting for world to stop..."); - // Wait for world to end + logUpdateTS = DateTime.MinValue; while (WorldManager.WorldActive) + { + logUpdateTS = LogStatusUpdate(logUpdateTS, "Waiting for world to stop..."); Thread.Sleep(10); + } log.Info("Saving OfflinePlayers that have unsaved changes..."); PlayerManager.SaveOfflinePlayersWithChanges(); - log.Info("Waiting for database queue to empty..."); - // Wait for the database queue to empty - while (DatabaseManager.Shard.QueueCount > 0) + logUpdateTS = DateTime.MinValue; + int shardQueueCount; + while ((shardQueueCount = DatabaseManager.Shard.QueueCount) > 0) + { + logUpdateTS = LogStatusUpdate(logUpdateTS, $"Waiting for database queue ({shardQueueCount}) to empty..."); Thread.Sleep(10); + } // Write exit to console/log log.Info($"Exiting at {DateTime.UtcNow}"); @@ -169,6 +203,17 @@ private static void ShutdownServer() Environment.Exit(Environment.ExitCode); } + private static DateTime LogStatusUpdate(DateTime logUpdateTS, string logMessage) + { + if (logUpdateTS == DateTime.MinValue || DateTime.UtcNow > logUpdateTS.ToUniversalTime()) + { + log.Info(logMessage); + logUpdateTS = DateTime.UtcNow.AddSeconds(10); + } + + return logUpdateTS; + } + private static DateTime NotifyPlayersOfPendingShutdown(DateTime lastNoticeTime, DateTime shutdownTime) { var notify = false; diff --git a/Source/ACE.Server/Network/Enum/SessionTerminationReason.cs b/Source/ACE.Server/Network/Enum/SessionTerminationReason.cs index f29fb4971e..6463d73890 100644 --- a/Source/ACE.Server/Network/Enum/SessionTerminationReason.cs +++ b/Source/ACE.Server/Network/Enum/SessionTerminationReason.cs @@ -27,6 +27,7 @@ public enum SessionTerminationReason WorldClosed, AbnormalSequenceReceived, AccountLoggedIn, + ServerShuttingDown, AccountBanned } public static class SessionTerminationReasonHelper @@ -52,6 +53,7 @@ public static class SessionTerminationReasonHelper "World is closed", "Client supplied an abnormal sequence", "Account was logged in, booting currently connected account in favor of new connection", + "Server is shutting down", "Account is banned" }; public static string GetDescription(this SessionTerminationReason reason) diff --git a/Source/ACE.Server/Network/Handlers/CharacterHandler.cs b/Source/ACE.Server/Network/Handlers/CharacterHandler.cs index 5387ae0202..b7465d46d9 100644 --- a/Source/ACE.Server/Network/Handlers/CharacterHandler.cs +++ b/Source/ACE.Server/Network/Handlers/CharacterHandler.cs @@ -31,6 +31,12 @@ public static void CharacterCreate(ClientMessage message, Session session) if (clientString != session.Account) return; + if (ServerManager.ShutdownInProgress) + { + session.SendCharacterError(CharacterError.LogonServerFull); + return; + } + if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Open || session.AccessLevel > AccessLevel.Player) CharacterCreateEx(message, session); else @@ -177,6 +183,12 @@ private static void CharacterCreateEx(ClientMessage message, Session session) [GameMessage(GameMessageOpcode.CharacterEnterWorldRequest, SessionState.AuthConnected)] public static void CharacterEnterWorldRequest(ClientMessage message, Session session) { + if (ServerManager.ShutdownInProgress) + { + session.SendCharacterError(CharacterError.LogonServerFull); + return; + } + if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Open || session.AccessLevel > AccessLevel.Player) session.Network.EnqueueSend(new GameMessageCharacterEnterWorldServerReady()); else @@ -190,6 +202,12 @@ public static void CharacterEnterWorld(ClientMessage message, Session session) string clientString = message.Payload.ReadString16L(); + if (ServerManager.ShutdownInProgress) + { + session.SendCharacterError(CharacterError.LogonServerFull); + return; + } + if (clientString != session.Account) { session.SendCharacterError(CharacterError.EnterGameCharacterNotOwned); @@ -252,6 +270,12 @@ public static void CharacterDelete(ClientMessage message, Session session) string clientString = message.Payload.ReadString16L(); uint characterSlot = message.Payload.ReadUInt32(); + if (ServerManager.ShutdownInProgress) + { + session.SendCharacterError(CharacterError.Delete); + return; + } + if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Closed && session.AccessLevel < AccessLevel.Advocate) { session.SendCharacterError(CharacterError.LogonServerFull); @@ -303,6 +327,12 @@ public static void CharacterRestore(ClientMessage message, Session session) { var guid = message.Payload.ReadUInt32(); + if (ServerManager.ShutdownInProgress) + { + session.SendCharacterError(CharacterError.EnterGameCouldntPlaceCharacter); + return; + } + if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Closed && session.AccessLevel < AccessLevel.Advocate) { session.SendCharacterError(CharacterError.LogonServerFull); diff --git a/Source/ACE.Server/Network/Managers/NetworkManager.cs b/Source/ACE.Server/Network/Managers/NetworkManager.cs index 226d96120c..38ce383db8 100644 --- a/Source/ACE.Server/Network/Managers/NetworkManager.cs +++ b/Source/ACE.Server/Network/Managers/NetworkManager.cs @@ -100,6 +100,11 @@ public static void ProcessPacket(ConnectionListener connectionListener, ClientPa log.InfoFormat("Login Request from {0} rejected. Server full.", endPoint); SendLoginRequestReject(connectionListener, endPoint, CharacterError.LogonServerFull); } + else if (ServerManager.ShutdownInProgress) + { + log.InfoFormat("Login Request from {0} rejected. Server is shutting down.", endPoint); + SendLoginRequestReject(connectionListener, endPoint, CharacterError.ServerCrash1); + } else if (ServerManager.ShutdownInitiated && (ServerManager.ShutdownTime - DateTime.UtcNow).TotalMinutes < 2) { log.InfoFormat("Login Request from {0} rejected. Server shutting down in less than 2 minutes.", endPoint); @@ -359,5 +364,13 @@ public static int DoSessionWork() } return sessionCount; } + + public static void DisconnectAllSessionsForShutdown() + { + foreach (var session in sessionMap) + { + session?.Terminate(SessionTerminationReason.ServerShuttingDown, new GameMessages.Messages.GameMessageCharacterError(CharacterError.ServerCrash1)); + } + } } } diff --git a/Source/ACE.Server/Network/Session.cs b/Source/ACE.Server/Network/Session.cs index 8d84926850..56de2fa4b4 100644 --- a/Source/ACE.Server/Network/Session.cs +++ b/Source/ACE.Server/Network/Session.cs @@ -175,6 +175,8 @@ public void SetPlayer(Player player) /// </summary> public void LogOffPlayer(bool forceImmediate = false) { + if (Player == null) return; + if (logOffRequestTime == DateTime.MinValue) { var result = Player.LogOut(false, forceImmediate); @@ -201,14 +203,17 @@ private void SendFinalLogOffMessages() Player = null; - Network.EnqueueSend(new GameMessageCharacterLogOff()); + if (!ServerManager.ShutdownInProgress) + { + Network.EnqueueSend(new GameMessageCharacterLogOff()); - CheckCharactersForDeletion(); + CheckCharactersForDeletion(); - Network.EnqueueSend(new GameMessageCharacterList(Characters, this)); + Network.EnqueueSend(new GameMessageCharacterList(Characters, this)); - GameMessageServerName serverNameMessage = new GameMessageServerName(ConfigManager.Config.Server.WorldName, PlayerManager.GetOnlineCount(), (int)ConfigManager.Config.Server.Network.MaximumAllowedSessions); - Network.EnqueueSend(serverNameMessage); + GameMessageServerName serverNameMessage = new GameMessageServerName(ConfigManager.Config.Server.WorldName, PlayerManager.GetOnlineCount(), (int)ConfigManager.Config.Server.Network.MaximumAllowedSessions); + Network.EnqueueSend(serverNameMessage); + } State = SessionState.AuthConnected; }