Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow [re]entry into world if shutdown is in progress #2794

Merged
merged 12 commits into from
Apr 12, 2020
67 changes: 56 additions & 11 deletions Source/ACE.Server/Managers/ServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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>
Expand Down Expand Up @@ -116,6 +122,8 @@ private static void ShutdownServer()
Thread.Sleep(10);
}

ShutdownInProgress = true;

PropertyManager.ResyncVariables();
PropertyManager.StopUpdating();

Expand All @@ -125,42 +133,68 @@ 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...");

// Queue unloading of all the landblocks
// 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}");
Expand All @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions Source/ACE.Server/Network/Enum/SessionTerminationReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public enum SessionTerminationReason
SendToSocketException,
WorldClosed,
AbnormalSequenceReceived,
AccountLoggedIn
AccountLoggedIn,
ServerShuttingDown
}
public static class SessionTerminationReasonHelper
{
Expand All @@ -50,7 +51,8 @@ public static class SessionTerminationReasonHelper
"MainSocket.SendTo exception occured",
"World is closed",
"Client supplied an abnormal sequence",
"Account was logged in, booting currently connected account in favor of new connection"
"Account was logged in, booting currently connected account in favor of new connection",
"Server is shutting down"
};
public static string GetDescription(this SessionTerminationReason reason)
{
Expand Down
30 changes: 30 additions & 0 deletions Source/ACE.Server/Network/Handlers/CharacterHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions Source/ACE.Server/Network/Managers/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
}
}
}
}
15 changes: 10 additions & 5 deletions Source/ACE.Server/Network/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down