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.
This commit is contained in:
Kamron Batman 2021-09-16 08:52:32 -07:00 committed by GitHub
parent bba5346a92
commit 7785f7e06c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 29 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();