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.
This commit is contained in:
Kamron Batman 2026-10-18 20:10:19 -07:00 • committed by GitHub
parent 9a25054809
commit 47f972ec9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 5 deletions

View file

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