diff --git a/Projects/Server/Timer/Timer.DelayCall.cs b/Projects/Server/Timer/Timer.DelayCall.cs index 8a5f93fe1..0dacb38de 100644 --- a/Projects/Server/Timer/Timer.DelayCall.cs +++ b/Projects/Server/Timer/Timer.DelayCall.cs @@ -144,11 +144,7 @@ public partial class Timer internal override void OnDetach() { - if (Running) - { - logger.Error("Timer is returned while still running!\n{StackTrace}", new StackTrace()); - return; - } + base.OnDetach(); if (_returnOnDetach) { @@ -177,10 +173,6 @@ public partial class Timer var timer = GetFromPool(); if (timer != null) { -#if DEBUG_TIMERS - logger.Information("Getting from pool: ({Count} / {Capacity})", _poolCount, _poolCapacity); -#endif - timer.Init(delay, interval, count); timer._continuation = callback; timer._returnOnDetach = false; @@ -191,8 +183,6 @@ public partial class Timer return timer; } - _timerPoolDepletionAmount++; - #if DEBUG_TIMERS logger.Warning("Timer pool depleted and timer was allocated.\n{StackTrace}", new StackTrace()); #endif diff --git a/Projects/Server/Timer/Timer.Pool.cs b/Projects/Server/Timer/Timer.Pool.cs index 96488ebbc..ed02dfe7d 100644 --- a/Projects/Server/Timer/Timer.Pool.cs +++ b/Projects/Server/Timer/Timer.Pool.cs @@ -21,33 +21,27 @@ namespace Server; public partial class Timer { - private const int _timerPoolDepletionThreshold = 128; // Maximum timers allocated in a single tick before we force adjust - private static int _timerPoolDepletionAmount; // Amount the pool has been depleted by private static int _maxPoolCapacity; private static int _poolCapacity; private static int _poolCount; private static DelayCallTimer _poolHead; - private static int _isRefilling; + private static bool _isRefilling; public static void CheckTimerPool() { - // Anything less than this threshold and we are ok with the number of allocations. - if (_timerPoolDepletionAmount < _timerPoolDepletionThreshold) + if (_poolCount > 0 || _isRefilling) { - _timerPoolDepletionAmount = 0; return; } - var growthFactor = Math.DivRem(_timerPoolDepletionAmount, _poolCapacity, out var rem); - var amountToGrow = _poolCapacity * (growthFactor + (rem > 0 ? 1 : 0)); + var amountToGrow = _poolCapacity * 2; var amountToRefill = Math.Min(_maxPoolCapacity, amountToGrow); var maximumHit = amountToGrow > amountToRefill ? " Maximum pool size has been reached." : ""; - var warningMessage = $"Timer pool depleted by {{Amount}}. Refilling with {{AmountRefill}}.{maximumHit}"; + var warningMessage = $"Refilling timer pool with {{AmountRefill}}.{maximumHit}"; - logger.Warning(warningMessage, _timerPoolDepletionAmount, amountToRefill); + logger.Warning(warningMessage, amountToRefill); RefillPoolAsync(amountToRefill); - _timerPoolDepletionAmount = 0; } public static void ConfigureTimerPool() @@ -64,9 +58,6 @@ public partial class Timer tail.Attach(_poolHead); _poolHead = head; _poolCount += amount; -#if DEBUG_TIMERS - logger.Information("Returning to pool. ({Count} / {Capacity})", _poolCount, _poolCapacity); -#endif } private static DelayCallTimer GetFromPool() @@ -109,10 +100,7 @@ public partial class Timer internal static async void RefillPoolAsync(int amountToRefill) { - if (Interlocked.CompareExchange(ref _isRefilling, 0, 1) == 1) - { - return; - } + _isRefilling = true; var (headTimer, tailTimer) = await Task.Run( () => @@ -125,6 +113,6 @@ public partial class Timer ReturnToPool(amountToRefill, headTimer, tailTimer); _poolCapacity = amountToRefill; - _isRefilling = 0; + _isRefilling = false; } } diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs index 8062264dd..d02874036 100644 --- a/Projects/Server/Timer/Timer.TimerWheel.cs +++ b/Projects/Server/Timer/Timer.TimerWheel.cs @@ -208,7 +208,7 @@ public partial class Timer public static void DumpInfo(TextWriter tw) { tw.WriteLine($"Date: {Core.Now.ToLocalTime()}\n"); - tw.WriteLine($"Pool - Count: {_poolCount - _timerPoolDepletionAmount}; Size {_poolCapacity}\n"); + tw.WriteLine($"Pool - Count: {_poolCount}; Capacity {_poolCapacity}\n"); var total = 0.0; var hash = new Dictionary(); diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs index cd163aef0..e736d0bda 100644 --- a/Projects/Server/Timer/Timer.cs +++ b/Projects/Server/Timer/Timer.cs @@ -14,6 +14,7 @@ *************************************************************************/ using System; +using System.Diagnostics; using Server.Diagnostics; using Server.Logging; @@ -51,6 +52,8 @@ public partial class Timer _nextTimer = null; _prevTimer = null; Next = Core.Now + Delay; + _ring = -1; + _slot = -1; var prof = GetProfile(); @@ -76,6 +79,29 @@ public partial class Timer public Timer Start() { + if (World.WorldState is WorldState.Initial or WorldState.Saving) + { + logger.Error( + "Attempted to start timer {Timer} ({HashCode}) while world is {State}\n{StackTrace}", + GetType(), + GetHashCode(), + World.WorldState, + new StackTrace() + ); + } + +#if THREADGUARD + if (Thread.CurrentThread != Core.Thread) + { + logger.Error( + "Attempted to start timer {Timer} ({HashCode}) from an invalid thread!\n{StackTrace}", + GetType(), + GetHashCode(), + new StackTrace() + ); + } +#endif + if (Running) { return this; @@ -97,6 +123,29 @@ public partial class Timer public void Stop() { + if (World.WorldState is WorldState.Initial or WorldState.Saving) + { + logger.Error( + "Attempted to stop timer {Timer} ({HashCode}) while world is {State}\n{StackTrace}", + GetType(), + GetHashCode(), + World.WorldState, + new StackTrace() + ); + } + +#if THREADGUARD + if (Thread.CurrentThread != Core.Thread) + { + logger.Error( + "Attempted to stop timer {Timer} ({HashCode}) from an invalid thread!\n{StackTrace}", + GetType(), + GetHashCode(), + new StackTrace() + ); + } +#endif + if (!Running) { return; @@ -132,9 +181,39 @@ public partial class Timer private void Attach(Timer timer) { +#if DEBUG_TIMERS + if (_prevTimer != null) + { + logger.Error( + "{Timer} ({HashCode}) attached with a previous timer already set!", + this, + GetHashCode() + ); + } + + if (_nextTimer != null) + { + logger.Error( + "{Timer} ({HashCode}) attached with a next timer already set!", + this, + GetHashCode() + ); + } +#endif _nextTimer = timer; + if (timer != null) { +#if DEBUG_TIMERS + if (timer._prevTimer != null) + { + logger.Error( + "{Timer} ({HashCode}) attached from with a previous timer already set!", + timer, + timer.GetHashCode() + ); + } +#endif timer._prevTimer = this; } } @@ -157,5 +236,13 @@ public partial class Timer internal virtual void OnDetach() { + if (Running) + { + logger.Error("{Timer} detached while still running!\n{StackTrace}", this, new StackTrace()); + return; + } + + _ring = -1; + _slot = -1; } } diff --git a/Projects/UOContent/Misc/BuffIcons.cs b/Projects/UOContent/Misc/BuffIcons.cs index cf9d93253..28813c35e 100644 --- a/Projects/UOContent/Misc/BuffIcons.cs +++ b/Projects/UOContent/Misc/BuffIcons.cs @@ -9,8 +9,7 @@ namespace Server { private TimerExecutionToken _timerToken; - public BuffInfo(BuffIcon iconID, int titleCliloc) - : this(iconID, titleCliloc, titleCliloc + 1) + public BuffInfo(BuffIcon iconID, int titleCliloc) : this(iconID, titleCliloc, titleCliloc + 1) { } @@ -105,8 +104,6 @@ namespace Server public long TimeStart { get; } - public TimerExecutionToken TimerToken => _timerToken; - public bool RetainThroughDeath { get; } public TextDefinition Args { get; } @@ -139,6 +136,7 @@ namespace Server public static void RemoveBuff(Mobile m, BuffInfo b) { + b._timerToken.Cancel(); (m as PlayerMobile)?.RemoveBuff(b); } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 8366e4d05..1e830f36c 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -4589,13 +4589,11 @@ namespace Server.Mobiles public void RemoveBuff(BuffIcon b) { - if (m_BuffTable == null || !m_BuffTable.Remove(b, out var info)) + if (m_BuffTable?.Remove(b) != true) { return; } - info.TimerToken.Cancel(); - if (NetState?.BuffIcon == true) { BuffInfo.SendRemoveBuffPacket(NetState, Serial, b);