From 55ae0f877839428c23d4d9e365fc0e189133c5f8 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Sat, 12 Sep 2020 00:05:38 -0700
Subject: [PATCH] Fixes exiting/crash handling/ctrl+c (#237)
- [X] Fixes handling of CTRL+C
- [X] Fixes crash guard (but should really be rewritten
- [X] Fixes exiting/restarting
Bumps release version
---
.../Packets/Old/Outgoing/EffectPacketTests.cs | 1 -
Projects/Server/AssemblyHandler.cs | 4 +-
Projects/Server/Main.cs | 153 ++++++++----------
Projects/Server/Network/MessagePumpService.cs | 2 +
Projects/Server/Network/NetState.cs | 88 +++++++++-
Projects/Server/Network/TcpServer.cs | 12 ++
Projects/UOContent/Misc/AutoRestart.cs | 10 +-
Projects/UOContent/Misc/Broadcasts.cs | 16 +-
Projects/UOContent/Misc/CrashGuard.cs | 90 ++++-------
9 files changed, 216 insertions(+), 160 deletions(-)
diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/EffectPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/EffectPacketTests.cs
index 3e938bc44..9766c677e 100644
--- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/EffectPacketTests.cs
+++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/EffectPacketTests.cs
@@ -85,7 +85,6 @@ namespace Server.Tests.Network.Packets
public void TestHuedEffect()
{
var effectType = EffectType.Moving;
- Serial serial = 0x4000;
Serial from = 0x1000;
Serial to = 0x2000;
var itemId = 0x100;
diff --git a/Projects/Server/AssemblyHandler.cs b/Projects/Server/AssemblyHandler.cs
index 9c0810621..daf4e8b89 100644
--- a/Projects/Server/AssemblyHandler.cs
+++ b/Projects/Server/AssemblyHandler.cs
@@ -111,9 +111,7 @@ namespace Server
public static string EnsureDirectory(string dir)
{
var path = Path.Combine(Core.BaseDirectory, dir);
-
- if (!Directory.Exists(path))
- Directory.CreateDirectory(path);
+ Directory.CreateDirectory(path);
return path;
}
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index 19ba7496c..2422f5f61 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -1,22 +1,17 @@
-/***************************************************************************
- * Main.cs
- * -------------------
- * begin : May 1, 2002
- * copyright : (C) The RunUO Software Team
- * email : info@runuo.com
- *
- * $Id$
- *
- ***************************************************************************/
-
-/***************************************************************************
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- ***************************************************************************/
+/*************************************************************************
+ * ModernUO *
+ * Copyright (C) 2019-2020 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: Main.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
using System;
using System.Collections.Generic;
@@ -42,7 +37,6 @@ namespace Server
private static bool m_Crashed;
private static Thread timerThread;
private static string m_BaseDirectory;
- private static string m_ExePath;
private static bool m_Profiling;
private static DateTime m_ProfileStart;
@@ -62,8 +56,6 @@ namespace Server
private static readonly double m_HighFrequency = 1000.0 / Stopwatch.Frequency;
private static readonly double m_LowFrequency = 1000.0 / TimeSpan.TicksPerSecond;
- internal static ConsoleEventHandler m_ConsoleEventHandler;
-
private static int m_CycleIndex = 1;
private static readonly float[] m_CyclesPerSecond = new float[100];
@@ -138,8 +130,6 @@ namespace Server
public static int ProcessorCount { get; private set; }
- public static string ExePath => m_ExePath ??= Assembly.Location;
-
public static string BaseDirectory
{
get
@@ -148,7 +138,7 @@ namespace Server
{
try
{
- m_BaseDirectory = ExePath;
+ m_BaseDirectory = Assembly.Location;
if (m_BaseDirectory.Length > 0)
{
@@ -165,7 +155,9 @@ namespace Server
}
}
- public static bool Closing { get; private set; }
+ public static CancellationTokenSource ClosingTokenSource { get; } = new CancellationTokenSource();
+
+ public static bool Closing => ClosingTokenSource.IsCancellationRequested;
public static float CyclesPerSecond => m_CyclesPerSecond[(m_CycleIndex - 1) % m_CyclesPerSecond.Length];
@@ -279,15 +271,6 @@ namespace Server
if (!close)
{
- try
- {
- // Close all listeners
- }
- catch
- {
- // ignored
- }
-
Console.WriteLine("This exception is fatal, press return to exit");
Console.ReadLine();
}
@@ -296,30 +279,56 @@ namespace Server
}
}
- private static bool OnConsoleEvent(ConsoleEventType type)
- {
- if (World.Saving || type == ConsoleEventType.CTRL_LOGOFF_EVENT)
- {
- return true;
- }
-
- Kill(); // Kill -> HandleClosed will handle waiting for the completion of flushing to disk
-
- return true;
- }
-
private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
- HandleClosed();
+ if (!Closing)
+ {
+ HandleClosed();
+ }
+ }
+
+ private static void Console_CancelKeyPressed(object sender, ConsoleCancelEventArgs e)
+ {
+ var keypress = e.SpecialKey switch
+ {
+ ConsoleSpecialKey.ControlBreak => "CTRL+BREAK",
+ _ => "CTRL+C"
+ };
+
+ Console.WriteLine("Core: Detected {0} pressed.", keypress);
+ e.Cancel = true;
+ Kill();
}
public static void Kill(bool restart = false)
{
+ if (Closing)
+ {
+ return;
+ }
+
HandleClosed();
if (restart)
{
- Process.Start(ExePath, Arguments);
+ if (IsWindows)
+ {
+ Process.Start("dotnet", Assembly.Location);
+ }
+ else
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet",
+ Arguments = Assembly.Location,
+ UseShellExecute = true
+ }
+ };
+
+ process.Start();
+ }
}
Process.Kill();
@@ -327,14 +336,9 @@ namespace Server
private static void HandleClosed()
{
- if (Closing)
- {
- return;
- }
+ ClosingTokenSource.Cancel();
- Closing = true;
-
- Console.Write("Exiting...");
+ Console.Write("Core: Shutting down...");
World.WaitForWriteCompletion();
@@ -426,11 +430,7 @@ namespace Server
Console.WriteLine("Core: Optimizing for {0} processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s");
}
- if (IsWindows)
- {
- m_ConsoleEventHandler = OnConsoleEvent;
- UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true);
- }
+ Console.CancelKeyPress += Console_CancelKeyPressed;
if (GCSettings.IsServerGC)
{
@@ -472,13 +472,14 @@ namespace Server
EventSink.InvokeServerStarted();
// Start net socket server
- var host = TcpServer.CreateWebHostBuilder().Build();
- var life = host.Services.GetRequiredService();
- life.ApplicationStopping.Register(() => { Kill(); });
+ _tcpHost = TcpServer.CreateWebHostBuilder().Build();
- host.Run();
+ // Run indefinitely and block
+ _tcpHost.RunAsync(ClosingTokenSource.Token).Wait();
}
+ private static IWebHost _tcpHost;
+
public static void RunEventLoop(IMessagePumpService messagePumpService)
{
try
@@ -542,7 +543,10 @@ namespace Server
{
var isItem = type.IsSubclassOf(typeof(Item));
- if (!isItem && !type.IsSubclassOf(typeof(Mobile))) return;
+ if (!isItem && !type.IsSubclassOf(typeof(Mobile)))
+ {
+ return;
+ }
if (isItem)
{
@@ -595,22 +599,5 @@ namespace Server
Parallel.ForEach(assembly.GetTypes(), VerifyType);
}
}
-
- internal enum ConsoleEventType
- {
- CTRL_C_EVENT,
- CTRL_BREAK_EVENT,
- CTRL_CLOSE_EVENT,
- CTRL_LOGOFF_EVENT = 5,
- CTRL_SHUTDOWN_EVENT
- }
-
- internal delegate bool ConsoleEventHandler(ConsoleEventType type);
-
- internal static class UnsafeNativeMethods
- {
- [DllImport("Kernel32")]
- internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler callback, bool add);
- }
}
}
diff --git a/Projects/Server/Network/MessagePumpService.cs b/Projects/Server/Network/MessagePumpService.cs
index e38744b2c..27f77ad76 100644
--- a/Projects/Server/Network/MessagePumpService.cs
+++ b/Projects/Server/Network/MessagePumpService.cs
@@ -40,7 +40,9 @@ namespace Server.Network
while (!m_WorkQueue.IsEmpty && count++ < 250)
{
if (!m_WorkQueue.TryDequeue(out var work))
+ {
break;
+ }
var seq = new ReadOnlySequence(work.MemoryOwner.Memory.Slice(0, work.Length));
work.OnReceive(work.State, new PacketReader(seq));
diff --git a/Projects/Server/Network/NetState.cs b/Projects/Server/Network/NetState.cs
index 5793ae19f..eda972410 100644
--- a/Projects/Server/Network/NetState.cs
+++ b/Projects/Server/Network/NetState.cs
@@ -181,36 +181,71 @@ namespace Server.Network
m_Version = value;
if (value >= m_Version70610)
+ {
ProtocolChanges = ProtocolChanges.Version70610;
+ }
+
if (value >= m_Version70500)
+ {
ProtocolChanges = ProtocolChanges.Version70500;
+ }
+
if (value >= m_Version704565)
+ {
ProtocolChanges = ProtocolChanges.Version704565;
+ }
else if (value >= m_Version70331)
+ {
ProtocolChanges = ProtocolChanges.Version70331;
+ }
else if (value >= m_Version70300)
+ {
ProtocolChanges = ProtocolChanges.Version70300;
+ }
else if (value >= m_Version70160)
+ {
ProtocolChanges = ProtocolChanges.Version70160;
+ }
else if (value >= m_Version70130)
+ {
ProtocolChanges = ProtocolChanges.Version70130;
+ }
else if (value >= m_Version7090)
+ {
ProtocolChanges = ProtocolChanges.Version7090;
+ }
else if (value >= m_Version7000)
+ {
ProtocolChanges = ProtocolChanges.Version7000;
+ }
else if (value >= m_Version60142)
+ {
ProtocolChanges = ProtocolChanges.Version60142;
+ }
else if (value >= m_Version6017)
+ {
ProtocolChanges = ProtocolChanges.Version6017;
+ }
else if (value >= m_Version6000)
+ {
ProtocolChanges = ProtocolChanges.Version6000;
+ }
else if (value >= m_Version502b)
+ {
ProtocolChanges = ProtocolChanges.Version502b;
+ }
else if (value >= m_Version500a)
+ {
ProtocolChanges = ProtocolChanges.Version500a;
+ }
else if (value >= m_Version407a)
+ {
ProtocolChanges = ProtocolChanges.Version407a;
- else if (value >= m_Version400a) ProtocolChanges = ProtocolChanges.Version400a;
+ }
+ else if (value >= m_Version400a)
+ {
+ ProtocolChanges = ProtocolChanges.Version400a;
+ }
}
}
@@ -275,7 +310,9 @@ namespace Server.Network
var info = ExpansionInfo.Table[i];
if (info.RequiredClient != null && Version >= info.RequiredClient || (Flags & info.ClientFlags) != 0)
+ {
return info;
+ }
}
return ExpansionInfo.GetInfo(Expansion.None);
@@ -292,21 +329,31 @@ namespace Server.Network
{
for (var i = Trades.Count - 1; i >= 0; --i)
{
- if (i >= Trades.Count) continue;
+ if (i >= Trades.Count)
+ {
+ continue;
+ }
var trade = Trades[i];
if (trade.From.Mobile.Deleted || trade.To.Mobile.Deleted || !trade.From.Mobile.Alive ||
!trade.To.Mobile.Alive || !trade.From.Mobile.InRange(trade.To.Mobile, 2) ||
- trade.From.Mobile.Map != trade.To.Mobile.Map) trade.Cancel();
+ trade.From.Mobile.Map != trade.To.Mobile.Map)
+ {
+ trade.Cancel();
+ }
}
}
public void CancelAllTrades()
{
for (var i = Trades.Count - 1; i >= 0; --i)
+ {
if (i < Trades.Count)
+ {
Trades[i].Cancel();
+ }
+ }
}
public void RemoveTrade(SecureTrade trade)
@@ -320,7 +367,10 @@ namespace Server.Network
{
var trade = Trades[i];
- if (trade.From.Mobile == m || trade.To.Mobile == m) return trade;
+ if (trade.From.Mobile == m || trade.To.Mobile == m)
+ {
+ return trade;
+ }
}
return null;
@@ -335,9 +385,15 @@ namespace Server.Network
var from = trade.From;
var to = trade.To;
- if (from.Mobile == Mobile && to.Mobile == m) return from.Container;
+ if (from.Mobile == Mobile && to.Mobile == m)
+ {
+ return @from.Container;
+ }
- if (from.Mobile == m && to.Mobile == Mobile) return to.Container;
+ if (from.Mobile == m && to.Mobile == Mobile)
+ {
+ return to.Container;
+ }
}
return null;
@@ -521,21 +577,29 @@ namespace Server.Network
while (true)
{
if (AsyncState.Paused)
+ {
continue;
+ }
var result = await inPipe.ReadAsync();
if (result.IsCanceled || result.IsCompleted)
+ {
return;
+ }
var seq = result.Buffer;
if (seq.IsEmpty)
+ {
break;
+ }
var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq);
if (pos <= 0)
+ {
break;
+ }
inPipe.AdvanceTo(seq.Slice(0, pos).End);
}
@@ -545,9 +609,9 @@ namespace Server.Network
Console.WriteLine(ex);
TraceException(ex);
}
- catch (Exception ex)
+ catch
{
- Console.WriteLine(ex);
+ // Console.WriteLine(ex);
}
finally
{
@@ -595,7 +659,9 @@ namespace Server.Network
{
var disposing = Interlocked.Exchange(ref m_Disposing, 1);
if (disposing == 1)
+ {
return;
+ }
try
{
@@ -620,7 +686,9 @@ namespace Server.Network
while (breakout++ < 200)
{
if (!m_Disposed.TryDequeue(out var ns))
+ {
break;
+ }
var m = ns.Mobile;
var a = ns.Account;
@@ -639,9 +707,13 @@ namespace Server.Network
ns.CityInfo = null;
if (a != null)
+ {
ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a);
+ }
else
+ {
ns.WriteConsole("Disconnected. [{0} Online]", TcpServer.Instances.Count);
+ }
}
}
diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs
index 132ad25e0..59180560b 100644
--- a/Projects/Server/Network/TcpServer.cs
+++ b/Projects/Server/Network/TcpServer.cs
@@ -54,15 +54,21 @@ namespace Server.Network
public static IPAddress[] GetListeningAddresses(IPEndPoint ipep)
{
if (m_ListeningAddresses != null)
+ {
return m_ListeningAddresses;
+ }
var list = new List();
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
{
var properties = adapter.GetIPProperties();
foreach (var unicast in properties.UnicastAddresses)
+ {
if (ipep.AddressFamily == unicast.Address.AddressFamily)
+ {
list.Add(unicast.Address);
+ }
+ }
}
return list.ToArray();
@@ -71,10 +77,16 @@ namespace Server.Network
private static void DisplayListener(IPEndPoint ipep)
{
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
+ {
foreach (var ip in m_ListeningAddresses)
+ {
Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port);
+ }
+ }
else
+ {
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
+ }
}
}
}
diff --git a/Projects/UOContent/Misc/AutoRestart.cs b/Projects/UOContent/Misc/AutoRestart.cs
index ab091af6f..7c8bebe2c 100644
--- a/Projects/UOContent/Misc/AutoRestart.cs
+++ b/Projects/UOContent/Misc/AutoRestart.cs
@@ -24,7 +24,9 @@ namespace Server.Misc
m_RestartTime = DateTime.UtcNow.Date + RestartTime;
if (m_RestartTime < DateTime.UtcNow)
+ {
m_RestartTime += TimeSpan.FromDays(1.0);
+ }
}
public static bool Restarting { get; private set; }
@@ -49,12 +51,12 @@ namespace Server.Misc
}
}
- private void Warning_Callback()
+ private static void Warning_Callback()
{
World.Broadcast(0x22, true, "The server is going down shortly.");
}
- private void Restart_Callback()
+ private static void Restart_Callback()
{
Core.Kill(true);
}
@@ -62,10 +64,14 @@ namespace Server.Misc
protected override void OnTick()
{
if (Restarting || !Enabled)
+ {
return;
+ }
if (DateTime.UtcNow < m_RestartTime)
+ {
return;
+ }
if (WarningDelay > TimeSpan.Zero)
{
diff --git a/Projects/UOContent/Misc/Broadcasts.cs b/Projects/UOContent/Misc/Broadcasts.cs
index d2b487e29..74eb2ad4f 100644
--- a/Projects/UOContent/Misc/Broadcasts.cs
+++ b/Projects/UOContent/Misc/Broadcasts.cs
@@ -22,14 +22,14 @@ namespace Server.Misc
public static void EventSink_Shutdown()
{
- /* try
- {
- World.Broadcast(0x35, true, "The server has shut down.");
- }
- catch
- {
- // ignored
- }*/
+ try
+ {
+ World.Broadcast(0x35, true, "The server has shut down.");
+ }
+ catch
+ {
+ // ignored
+ }
}
}
}
diff --git a/Projects/UOContent/Misc/CrashGuard.cs b/Projects/UOContent/Misc/CrashGuard.cs
index 0ac3b060d..d1d22de41 100644
--- a/Projects/UOContent/Misc/CrashGuard.cs
+++ b/Projects/UOContent/Misc/CrashGuard.cs
@@ -8,32 +8,38 @@ namespace Server.Misc
{
public static class CrashGuard
{
- private static readonly bool Enabled = true;
- private static readonly bool SaveBackup = true;
- private static readonly bool RestartServer = true;
- private static readonly bool GenerateReport = true;
+ // TODO: Make this configurable
+ private const bool Enabled = true;
+ private const bool SaveBackup = true;
+ private const bool RestartServer = true;
+ private const bool GenerateReport = true;
public static void Initialize()
{
- if (Enabled) // If enabled, register our crash event handler
+ if (Enabled)
+ {
EventSink.ServerCrashed += CrashGuard_OnCrash;
+ }
}
public static void CrashGuard_OnCrash(ServerCrashedEventArgs e)
{
if (GenerateReport)
+ {
GenerateCrashReport(e);
+ }
World.WaitForWriteCompletion();
if (SaveBackup)
+ {
Backup();
+ }
- /*if (Core.Service)
- e.Close = true;
- else */
if (RestartServer)
+ {
Restart(e);
+ }
}
private static void SendEmail(string filePath)
@@ -43,29 +49,13 @@ namespace Server.Misc
Email.SendCrashEmail(filePath);
}
- private static string GetRoot()
- {
- try
- {
- return Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
- }
- catch
- {
- return "";
- }
- }
-
- private static string Combine(string path1, string path2) => path1.Length == 0 ? path2 : Path.Combine(path1, path2);
-
private static void Restart(ServerCrashedEventArgs e)
{
- var root = GetRoot();
-
Console.Write("Crash: Restarting...");
try
{
- Process.Start(Core.ExePath, Core.Arguments);
+ Process.Start(Core.Assembly.Location, Core.Arguments);
Console.WriteLine("done");
e.Close = true;
@@ -76,26 +66,20 @@ namespace Server.Misc
}
}
- private static void CreateDirectory(string path)
- {
- if (!Directory.Exists(path))
- Directory.CreateDirectory(path);
- }
-
- private static void CreateDirectory(string path1, string path2)
- {
- CreateDirectory(Combine(path1, path2));
- }
-
private static void CopyFile(string rootOrigin, string rootBackup, string path)
{
- var originPath = Combine(rootOrigin, path);
- var backupPath = Combine(rootBackup, path);
+ var originPath = Path.Combine(rootOrigin, path);
+ if (!File.Exists(originPath))
+ {
+ return;
+ }
+
+ var backupPath = Path.Combine(rootBackup, path);
+ Directory.CreateDirectory(Path.GetDirectoryName(backupPath));
try
{
- if (File.Exists(originPath))
- File.Copy(originPath, backupPath);
+ File.Copy(originPath, backupPath);
}
catch
{
@@ -111,17 +95,9 @@ namespace Server.Misc
{
var timeStamp = GetTimeStamp();
- var root = GetRoot();
- var rootBackup = Combine(root, $"Backups/Crashed/{timeStamp}/");
- var rootOrigin = Combine(root, "Saves/");
-
- // Create new directories
- CreateDirectory(rootBackup);
- CreateDirectory(rootBackup, "Accounts/");
- CreateDirectory(rootBackup, "Items/");
- CreateDirectory(rootBackup, "Mobiles/");
- CreateDirectory(rootBackup, "Guilds/");
- CreateDirectory(rootBackup, "Regions/");
+ var root = Core.BaseDirectory;
+ var rootBackup = Path.Combine(root, $"Backups/Crashed/{timeStamp}/");
+ var rootOrigin = Path.Combine(root, "Saves/");
// Copy files
CopyFile(rootOrigin, rootBackup, "Accounts/Accounts.xml");
@@ -157,12 +133,12 @@ namespace Server.Misc
var timeStamp = GetTimeStamp();
var fileName = $"Crash {timeStamp}.log";
- var root = GetRoot();
- var filePath = Combine(root, fileName);
+ var root = Core.BaseDirectory;
+ var filePath = Path.Combine(root, fileName);
using (var op = new StreamWriter(filePath))
{
- var ver = Core.Assembly.GetName().Version ?? new Version("0.0.0.0");
+ var ver = Core.Version;
op.WriteLine("Server Crash Report");
op.WriteLine("===================");
@@ -170,7 +146,7 @@ namespace Server.Misc
op.WriteLine($"ModernUO Version {ver.Major}.{ver.Minor}, Build {ver.Build}.{ver.Revision}");
op.WriteLine("Operating System: {0}", Environment.OSVersion);
op.WriteLine(".NET Framework: {0}", Environment.Version);
- op.WriteLine("Time: {0}", DateTime.UtcNow);
+ op.WriteLine("Time: {0}", timeStamp);
try
{
@@ -209,12 +185,16 @@ namespace Server.Misc
op.Write("+ {0}:", state);
if (state.Account is Account a)
+ {
op.Write(" (account = {0})", a.Username);
+ }
var m = state.Mobile;
if (m != null)
+ {
op.Write(" (mobile = 0x{0:X} '{1}')", m.Serial.Value, m.Name);
+ }
op.WriteLine();
}