From 7785f7e06cc2896366553fa980d53027f97965fc Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 16 Sep 2021 08:52:32 -0700 Subject: [PATCH] fix: Fixes timer wheel edge cases (#782) 1. Stopping a different timer, on the same slot as the timer being executed during OnTick * Adds a check for the timer wheel currently being executed and does not detach the timer on Stop(). 1. Timers are added to the current slot if they need 4095 slots, before the chain is fully detached/executed * Removes all chains that will be executed from the timer wheel before executing. --- Projects/Server/Timer/Timer.DelayCall.cs | 2 +- Projects/Server/Timer/Timer.TimerWheel.cs | 69 +++++++++++++++-------- Projects/Server/Timer/Timer.cs | 13 +++-- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/Projects/Server/Timer/Timer.DelayCall.cs b/Projects/Server/Timer/Timer.DelayCall.cs index 11d6b84c6..81fb9d5b6 100644 --- a/Projects/Server/Timer/Timer.DelayCall.cs +++ b/Projects/Server/Timer/Timer.DelayCall.cs @@ -157,7 +157,7 @@ namespace Server { if (Running) { - logger.Error($"Timer is returned while still running! {new StackTrace()}"); + logger.Error($"Timer is returned while still running!\n{new StackTrace()}"); return; } diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs index 89c7d75bd..b0bf65003 100644 --- a/Projects/Server/Timer/Timer.TimerWheel.cs +++ b/Projects/Server/Timer/Timer.TimerWheel.cs @@ -28,11 +28,12 @@ namespace Server private const int _tickRatePowerOf2 = 3; private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms - private static long _lastTickTurned = -1; - private static readonly Timer[][] _rings = new Timer[_ringLayers][]; private static readonly int[] _ringIndexes = new int[_ringLayers]; + private static long _lastTickTurned = -1; + private static bool _timerWheelExecuting; + public static void Init(long tickCount) { _lastTickTurned = tickCount; @@ -61,29 +62,52 @@ namespace Server private static bool Turn() { bool events = false; + _timerWheelExecuting = true; + var turnNextWheel = false; + + var _executingRings = new Timer[_ringLayers]; + + // Detach the chain from the timer wheel. This allows adding timers to the same slot during execution. + for (var i = 0; i < _ringLayers; i++) + { + if (i == 0 || turnNextWheel) + { + var ringIndex = ++_ringIndexes[i]; + turnNextWheel = ringIndex >= _ringSize; + + if (turnNextWheel) + { + ringIndex = _ringIndexes[i] = 0; + } + + _executingRings[i] = _rings[i][ringIndex]; + _rings[i][ringIndex] = null; + } + else + { + _executingRings[i] = null; + } + } for (var i = 0; i < _ringLayers; i++) { - // Increment the ring index, then get the timer. - var ringIndex = ++_ringIndexes[i]; - bool turnNextWheel = ringIndex >= _ringSize; - - if (turnNextWheel) + var timer = _executingRings[i]; + if (timer == null) { - ringIndex = _ringIndexes[i] = 0; + continue; } - var timer = _rings[i][ringIndex]; - if (timer != null) + events = true; + + do { - events = true; + var next = timer._nextTimer; - do + timer.Detach(); + + // Check to see if it's running just in case it was stopped by another timer + if (timer.Running) { - var next = _rings[i][ringIndex] = timer._nextTimer; - - timer.Detach(); - if (i > 0 && timer._remaining > 0) { // Promote @@ -93,17 +117,14 @@ namespace Server { Execute(timer); } + } - timer = next; - } while (timer != null); - } - - if (!turnNextWheel) - { - break; - } + timer = next; + } while (timer != null); } + _timerWheelExecuting = false; + return events; } diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs index 7d10bf441..176cf5748 100644 --- a/Projects/Server/Timer/Timer.cs +++ b/Projects/Server/Timer/Timer.cs @@ -102,13 +102,18 @@ namespace Server return; } - // We are at the head - if (_rings[_ring][_slot] == this) + // Do not detach if we are in the middle of executing the timer wheel for this ring/slot + if (!_timerWheelExecuting || _ringIndexes[_ring] != _slot) { - _rings[_ring][_slot] = _nextTimer; + // We are at the head + if (_rings[_ring][_slot] == this) + { + _rings[_ring][_slot] = _nextTimer; + } + + Detach(); } - Detach(); Running = false; Version++; var prof = GetProfile();