From 47f972ec9d27a8199fb6bf30114f0e862475ebd6 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:10:19 -0700 Subject: [PATCH] fix: ClearAllTimers stops every timer in a slot, not just the first (#2723) ## Summary `Timer.ClearAllTimers` walked each wheel slot with a `do/while` that read `node._nextTimer` but never advanced `node`. `Stop()` unlinks the head from its slot, so the second pass read a null successor from the same, now-detached node and exited. Only the first timer in every slot was stopped; the rest kept running. The loop now reads the successor, stops the node, and advances. Nothing in the repo calls `ClearAllTimers` today, so no running server is affected. It is public API for shards and tests. ## Tests - `TimerTests.ClearAllTimers_StopsEveryTimerInASlot` puts three timers in the same slot, clears them, asserts none is `Running`, and slices past their due time. It fails without the fix. - Server.Tests: all pass. --- .../Server.Tests/Tests/Timer/TimerTests.cs | 24 +++++++++++++++++++ Projects/Server/Timer/Timer.TimerWheel.cs | 11 +++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Projects/Server.Tests/Tests/Timer/TimerTests.cs b/Projects/Server.Tests/Tests/Timer/TimerTests.cs index 9f610de50..e7a9b6241 100644 --- a/Projects/Server.Tests/Tests/Timer/TimerTests.cs +++ b/Projects/Server.Tests/Tests/Timer/TimerTests.cs @@ -85,6 +85,30 @@ public class TimerTests Assert.Equal(2, timerTicks.ExecutedCount); } + [Fact] + public void ClearAllTimers_StopsEveryTimerInASlot() + { + Timer.Init(0); + + var timers = new Timer[3]; + for (var i = 0; i < timers.Length; i++) + { + timers[i] = Timer.DelayCall(TimeSpan.FromMilliseconds(80), () => Assert.Fail("cleared timer fired")); + } + + Timer.ClearAllTimers(0); + + for (var i = 0; i < timers.Length; i++) + { + Assert.False(timers[i].Running); + } + + for (var tick = 8; tick <= 160; tick += 8) + { + Timer.Slice(tick); + } + } + private class TimerTicks { public long ExpectedTicks; diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs index 2ab3ce547..0395bf3fe 100644 --- a/Projects/Server/Timer/Timer.TimerWheel.cs +++ b/Projects/Server/Timer/Timer.TimerWheel.cs @@ -321,13 +321,14 @@ public partial class Timer for (var i = 0; i < _ringSize; i++) { var node = t[i]; - Timer next; - do + while (node != null) { - next = node?._nextTimer; - node?.Stop(); - } while (next != null); + // Stop() unlinks the node, so read its successor first. + var next = node._nextTimer; + node.Stop(); + node = next; + } } } }