From 5d50a5a37cdca2a93d14ea1521d28af7bdb74161 Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 01:34:04 +0100 Subject: [PATCH 1/6] + Added simple HttpListener for the integrated RunUO status page. * HttpListener will serve the status page to a URI in the format of http:///status/ * Port 80 may need to be white-listed for external requests to be served. --- Scripts/Misc/WebStatus.cs | 229 +++++++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 77 deletions(-) diff --git a/Scripts/Misc/WebStatus.cs b/Scripts/Misc/WebStatus.cs index 4401d6728..7aac70469 100644 --- a/Scripts/Misc/WebStatus.cs +++ b/Scripts/Misc/WebStatus.cs @@ -1,118 +1,193 @@ +#region References using System; using System.IO; +using System.Linq; +using System.Net; using System.Text; -using Server; -using Server.Network; + using Server.Guilds; +using Server.Network; +#endregion namespace Server.Misc { public class StatusPage : Timer { - public static bool Enabled = false; + public static readonly bool Enabled = true; + + private static HttpListener _Listener; + + private static string _StatusPage = String.Empty; + private static byte[] _StatusBuffer = new byte[0]; + + private static readonly object _StatusLock = new object(); public static void Initialize() { - if ( Enabled ) - new StatusPage().Start(); + if (!Enabled) + { + return; + } + + new StatusPage().Start(); + + Listen(); } - public StatusPage() : base( TimeSpan.FromSeconds( 5.0 ), TimeSpan.FromSeconds( 60.0 ) ) + private static void Listen() { - Priority = TimerPriority.FiveSeconds; + if (!HttpListener.IsSupported) + { + return; + } + + if (_Listener == null) + { + _Listener = new HttpListener(); + _Listener.Prefixes.Add("http://*:80/status/"); + _Listener.Start(); + } + else if (!_Listener.IsListening) + { + _Listener.Start(); + } + + if (_Listener.IsListening) + { + _Listener.BeginGetContext(ListenerCallback, null); + } } - private static string Encode( string input ) + private static void ListenerCallback(IAsyncResult result) { - StringBuilder sb = new StringBuilder( input ); + try + { + var context = _Listener.EndGetContext(result); - sb.Replace( "&", "&" ); - sb.Replace( "<", "<" ); - sb.Replace( ">", ">" ); - sb.Replace( "\"", """ ); - sb.Replace( "'", "'" ); + byte[] buffer; + + lock (_StatusLock) + { + buffer = _StatusBuffer; + } + + context.Response.ContentLength64 = buffer.Length; + context.Response.OutputStream.Write(buffer, 0, buffer.Length); + context.Response.OutputStream.Close(); + } + catch + { } + + Listen(); + } + + private static string Encode(string input) + { + var sb = new StringBuilder(input); + + sb.Replace("&", "&"); + sb.Replace("<", "<"); + sb.Replace(">", ">"); + sb.Replace("\"", """); + sb.Replace("'", "'"); return sb.ToString(); } + public StatusPage() + : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(60.0)) + { + Priority = TimerPriority.FiveSeconds; + } + protected override void OnTick() { - if ( !Directory.Exists( "web" ) ) - Directory.CreateDirectory( "web" ); - - using ( StreamWriter op = new StreamWriter( "web/status.html" ) ) + if (!Directory.Exists("web")) { - op.WriteLine( "" ); - op.WriteLine( " " ); - op.WriteLine( " RunUO Server Status"); - op.WriteLine( " " ); - op.WriteLine( " " ); - op.WriteLine( "

RunUO Server Status

" ); - op.WriteLine( " Online clients:
" ); - op.WriteLine( " " ); - op.WriteLine( " " ); - op.WriteLine( " " ); - op.WriteLine( " " ); + Directory.CreateDirectory("web"); + } - foreach ( NetState state in NetState.Instances ) + using (var op = new StreamWriter("web/status.html")) + { + op.WriteLine(""); + op.WriteLine(""); + op.WriteLine(" "); + op.WriteLine(" " + ServerList.ServerName + " Server Status"); + op.WriteLine(" "); + op.WriteLine(" "); + op.WriteLine(" "); + op.WriteLine("

RunUO Server Status

"); + op.WriteLine("

Online clients

"); + op.WriteLine("
NameLocationKillsKarma / Fame
"); + op.WriteLine(" "); + + var index = 0; + + foreach (var m in NetState.Instances.Where(state => state.Mobile != null).Select(state => state.Mobile)) { - Mobile m = state.Mobile; + ++index; - if ( m != null ) + var g = m.Guild as Guild; + + op.Write(" " ); + op.Write(Encode(g.Abbreviation)); + + op.Write(']'); } + else + { + op.Write(Encode(m.Name)); + } + + op.Write(""); } - op.WriteLine( " " ); - op.WriteLine( "
NameLocationKillsKarma/Fame
"); + + if (g != null) { - Guild g = m.Guild as Guild; + op.Write(Encode(m.Name)); + op.Write(" ["); - op.Write( "
" ); + var title = m.GuildTitle; - if ( g != null ) + title = title != null ? title.Trim() : String.Empty; + + if (title.Length > 0) { - op.Write( Encode( m.Name ) ); - op.Write( " [" ); - - string title = m.GuildTitle; - - if ( title != null ) - title = title.Trim(); - else - title = ""; - - if ( title.Length > 0 ) - { - op.Write( Encode( title ) ); - op.Write( ", " ); - } - - op.Write( Encode( g.Abbreviation ) ); - - op.Write( ']' ); - } - else - { - op.Write( Encode( m.Name ) ); + op.Write(Encode(title)); + op.Write(", "); } - op.Write( "" ); - op.Write( m.X ); - op.Write( ", " ); - op.Write( m.Y ); - op.Write( ", " ); - op.Write( m.Z ); - op.Write( " (" ); - op.Write( m.Map ); - op.Write( ")" ); - op.Write( m.Kills ); - op.Write( "" ); - op.Write( m.Karma ); - op.Write( " / " ); - op.Write( m.Fame ); - op.WriteLine( "
"); + op.Write(m.X); + op.Write(", "); + op.Write(m.Y); + op.Write(", "); + op.Write(m.Z); + op.Write(" ("); + op.Write(m.Map); + op.Write(")"); + op.Write(m.Kills); + op.Write(""); + op.Write(m.Karma); + op.Write(" / "); + op.Write(m.Fame); + op.WriteLine("
" ); - op.WriteLine( " " ); - op.WriteLine( "" ); + op.WriteLine(" "); + op.WriteLine(" "); + op.WriteLine(" "); + op.WriteLine(""); + } + + lock (_StatusLock) + { + _StatusPage = File.ReadAllText("web/status.html"); + _StatusBuffer = Encoding.UTF8.GetBytes(_StatusPage); } } } From 887ca5d3419c6d98edbf64215e0b9e33da801a20 Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 01:40:09 +0100 Subject: [PATCH 2/6] + Don't allow processing of the Timer changed queue while the world is loading or saving. * Removes the risk of inconsistency [warnings] generated by timers that tick with handlers that modify World.Items and World.Mobiles during I/O. + Update description of Core.TickCount. --- RunUO.exe.config | 20 +++++++++++--------- Server/Main.cs | 16 +++++----------- Server/Timer.cs | 6 ++++++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/RunUO.exe.config b/RunUO.exe.config index 5106eb6a4..3fe506032 100644 --- a/RunUO.exe.config +++ b/RunUO.exe.config @@ -1,10 +1,12 @@ - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/Server/Main.cs b/Server/Main.cs index 1bf17cc35..14f029207 100644 --- a/Server/Main.cs +++ b/Server/Main.cs @@ -107,23 +107,17 @@ namespace Server public static Thread Thread { get { return m_Thread; } } public static MultiTextWriter MultiConsoleOut { get { return m_MultiConOut; } } - /* DateTime.Now and DateTime.UtcNow are based on actual system clock time. + /* + * DateTime.Now and DateTime.UtcNow are based on actual system clock time. * The resolution is acceptable but large clock jumps are possible and cause issues. * GetTickCount and GetTickCount64 have poor resolution. * GetTickCount64 is unavailable on Windows XP and Windows Server 2003. * Stopwatch.GetTimestamp() (QueryPerformanceCounter) is high resolution, but - * somewhat expensive to call and unreliable with certain system configurations. + * somewhat expensive to call because of its defference to DateTime.Now, + * which is why Stopwatch has been used to verify HRT before calling GetTimestamp(), + * enabling the usage of DateTime.UtcNow instead. */ - /* The following implementation contains an effective substitute for GetTickCount64 that - * is reliable as long as it is retrieved once every 2^32 ms (~49 days). - */ - - /* We don't really need this, but it may be useful in the future. - private static ThreadLocal _HighOrder = new ThreadLocal(); - private static ThreadLocal _LastTickCount = new ThreadLocal(); - */ - private static readonly bool _HighRes = Stopwatch.IsHighResolution; private static readonly double _HighFrequency = 1000.0 / Stopwatch.Frequency; diff --git a/Server/Timer.cs b/Server/Timer.cs index 6c1170eb6..f3aa7a21e 100644 --- a/Server/Timer.cs +++ b/Server/Timer.cs @@ -313,6 +313,12 @@ namespace Server while ( !Core.Closing ) { + if (World.Loading || World.Saving) + { + m_Signal.WaitOne(1, false); + continue; + } + ProcessChanged(); loaded = false; From 413f9844b7150ca80c13717624432a78d25b5b80 Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 01:43:12 +0100 Subject: [PATCH 3/6] - Disable WebStatus by default. --- Scripts/Misc/WebStatus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Misc/WebStatus.cs b/Scripts/Misc/WebStatus.cs index 7aac70469..ae8895929 100644 --- a/Scripts/Misc/WebStatus.cs +++ b/Scripts/Misc/WebStatus.cs @@ -13,7 +13,7 @@ namespace Server.Misc { public class StatusPage : Timer { - public static readonly bool Enabled = true; + public static readonly bool Enabled = false; private static HttpListener _Listener; From af86aa23c7c6032b8a22cffb1409cf8a6b62c99d Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 01:51:26 +0100 Subject: [PATCH 4/6] + Update project settings. * Scripts project assembly name changed to "Scripts.CS" and build output targets the "Scripts/Output/" directory to mimic RunUO's ScriptCompiler behaviour. --- Scripts/Scripts.csproj | 21 ++++++++++++--------- Server/Server.csproj | 16 ++++++++++------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Scripts/Scripts.csproj b/Scripts/Scripts.csproj index 558fdb002..dd5452d9c 100644 --- a/Scripts/Scripts.csproj +++ b/Scripts/Scripts.csproj @@ -6,7 +6,7 @@ {DAE872E6-6899-427C-A75D-AD52526EBCDA} Library false - RunUO Scripts + Scripts.CS v4.0 512 @@ -17,17 +17,19 @@ full false Output\ - DEBUG;TRACE + TRACE;DEBUG;NEWTIMERS, NEWPARENT prompt 4 + false pdbonly true Output\ - TRACE + TRACE;NEWTIMERS, NEWPARENT prompt 4 + false Server @@ -35,21 +37,22 @@ true Output\ - DEBUG;TRACE + TRACE;DEBUG;NEWTIMERS, NEWPARENT full - x64 + AnyCPU prompt MinimumRecommendedRules.ruleset - true + false - bin\x64\Release\ - TRACE + Output\ + TRACE;NEWTIMERS, NEWPARENT true pdbonly - x64 + AnyCPU prompt MinimumRecommendedRules.ruleset + false diff --git a/Server/Server.csproj b/Server/Server.csproj index 6ddcb8dc3..2529bfcac 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -39,42 +39,46 @@ true ..\ - DEBUG;TRACE + TRACE;DEBUG;NEWTIMERS, NEWPARENT true full AnyCPU prompt MinimumRecommendedRules.ruleset + false ..\ - TRACE + TRACE;NEWTIMERS, NEWPARENT true true pdbonly AnyCPU prompt MinimumRecommendedRules.ruleset + false true ..\ - DEBUG;TRACE + TRACE;DEBUG;NEWTIMERS, NEWPARENT true full - x64 + AnyCPU prompt MinimumRecommendedRules.ruleset + false ..\ - TRACE + TRACE;NEWTIMERS, NEWPARENT true true pdbonly - x64 + AnyCPU prompt MinimumRecommendedRules.ruleset + false From a32c4573adf4b27ca37bc1b1622632687bf8c372 Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 02:13:49 +0100 Subject: [PATCH 5/6] + Execute and store the results of the Linq query as a List to prevent collection modified exceptions. * The List is a member of PooledEnumerable and is thus pooled along with it. --- Server/Map.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Server/Map.cs b/Server/Map.cs index f4580c557..1a0c30b1a 100644 --- a/Server/Map.cs +++ b/Server/Map.cs @@ -1743,7 +1743,7 @@ namespace Server if (e != null) { - e._Pool = pool; + e._Pool.AddRange(pool); } else { @@ -1754,11 +1754,11 @@ namespace Server } private bool _IsDisposed; - private IEnumerable _Pool; + private List _Pool; public PooledEnumerable(IEnumerable pool) { - _Pool = pool; + _Pool = new List(pool); } IEnumerator IEnumerable.GetEnumerator() @@ -1778,7 +1778,7 @@ namespace Server return; } - _Pool = null; + _Pool.Clear(); lock (((ICollection)_Buffer).SyncRoot) { @@ -1789,6 +1789,9 @@ namespace Server public void Dispose() { _IsDisposed = true; + + _Pool.Clear(); + _Pool.TrimExcess(); _Pool = null; } } From 803cd14fc367f8ea3b4cff2629761cbfcd79660d Mon Sep 17 00:00:00 2001 From: Vorspire Date: Sun, 23 Aug 2015 02:36:34 +0100 Subject: [PATCH 6/6] + Update project settings. --- Scripts/Scripts.csproj | 4 ++-- Server/Server.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Scripts/Scripts.csproj b/Scripts/Scripts.csproj index dd5452d9c..a13e646dd 100644 --- a/Scripts/Scripts.csproj +++ b/Scripts/Scripts.csproj @@ -39,7 +39,7 @@ Output\ TRACE;DEBUG;NEWTIMERS, NEWPARENT full - AnyCPU + x64 prompt MinimumRecommendedRules.ruleset false @@ -49,7 +49,7 @@ TRACE;NEWTIMERS, NEWPARENT true pdbonly - AnyCPU + x64 prompt MinimumRecommendedRules.ruleset false diff --git a/Server/Server.csproj b/Server/Server.csproj index 2529bfcac..62bfacff4 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -64,7 +64,7 @@ TRACE;DEBUG;NEWTIMERS, NEWPARENT true full - AnyCPU + x64 prompt MinimumRecommendedRules.ruleset false @@ -75,7 +75,7 @@ true true pdbonly - AnyCPU + x64 prompt MinimumRecommendedRules.ruleset false