From e58ee11997b30ea45ea6710759d769348f2ef824 Mon Sep 17 00:00:00 2001 From: mapogolions Date: Sun, 7 Jan 2024 01:54:21 +0500 Subject: [PATCH 1/2] reset gate under the lock --- .../FakeTimeProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs index f6ef2a32fd1..bf0ab40dd5d 100644 --- a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs +++ b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs @@ -234,13 +234,13 @@ private void WakeWaiters() candidate = waiter; } } - } - if (candidate == null) - { - // didn't find a candidate to wake, we're done - _wakeWaitersGate = 0; - return; + if (candidate == null) + { + // didn't find a candidate to wake, we're done + _wakeWaitersGate = 0; + return; + } } var oldTicks = _now.Ticks; From c546b4d791407c1fdfc62d1963f513596ed3cd23 Mon Sep 17 00:00:00 2001 From: mapogolions Date: Tue, 9 Jan 2024 22:35:18 +0500 Subject: [PATCH 2/2] add a unit test to reproduce the context-switching effect on the timer callback --- .../FakeTimeProvider.cs | 3 ++ .../FakeTimeProviderTests.cs | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs index bf0ab40dd5d..d4b8caa60ba 100644 --- a/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs +++ b/src/Libraries/Microsoft.Extensions.TimeProvider.Testing/FakeTimeProvider.cs @@ -194,6 +194,8 @@ internal void AddWaiter(Waiter waiter, long dueTime) WakeWaiters(); } + internal event EventHandler? GateOpening; + private void WakeWaiters() { if (Interlocked.CompareExchange(ref _wakeWaitersGate, 1, 0) == 1) @@ -238,6 +240,7 @@ private void WakeWaiters() if (candidate == null) { // didn't find a candidate to wake, we're done + GateOpening?.Invoke(this, EventArgs.Empty); _wakeWaitersGate = 0; return; } diff --git a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs index 66561a30af9..055ac5dc496 100644 --- a/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.TimeProvider.Testing.Tests/FakeTimeProviderTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Time.Testing; @@ -361,4 +362,31 @@ public void AdvanceTimeInCallback() Assert.True(true, "Yay, we didn't enter an infinite loop!"); } + + [Fact] + public void ShouldResetGateUnderLock_PreventingContextSwitching_AffectionOnTimerCallback() + { + // Arrange + var provider = new FakeTimeProvider { AutoAdvanceAmount = TimeSpan.FromSeconds(2) }; + var calls = new List(); + using var timer = provider.CreateTimer(calls.Add, "timer-1", TimeSpan.FromSeconds(3), TimeSpan.Zero); + var th = new Thread(() => provider.GetUtcNow()); + provider.GateOpening += (_, _) => + { + if (!th.IsAlive) + { + th.Start(); + } + + // use a timeout to prevent deadlock + th.Join(TimeSpan.FromMilliseconds(200)); + }; + + // Act + provider.GetUtcNow(); + th.Join(); + + // Assert + Assert.Single(calls); + } }