From bdde35047da1fd3806b3eace7fac0571119f35a0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 27 Mar 2023 22:08:53 -0400 Subject: [PATCH] Fix infinite timeout passed to CancellationTokenSource.CancelAfter The recent change to introduce ITimer broke CTS.CancelAfter(Timeout.InfiniteTimeSpan). The TimeSpan's milliseconds were extracted as a uint but then ended up being cast to a long, such that rather than representing -1 milliseconds, it represented 4294967295 milliseconds. With the uint representation in Timer, Timeout.UnsignedInfinite needs to be special-cased. --- .../Threading/CancellationTokenSource.cs | 4 ++- .../tests/CancellationTokenTests.cs | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs index d6277386bd1c78..ec6f8e3c200815 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs @@ -446,7 +446,9 @@ private void CancelAfter(uint millisecondsDelay) } } - timer.Change(TimeSpan.FromMilliseconds(millisecondsDelay), Timeout.InfiniteTimeSpan); + timer.Change( + millisecondsDelay == Timeout.UnsignedInfinite ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(millisecondsDelay), + Timeout.InfiniteTimeSpan); } /// diff --git a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs index cd01a0e0a9a577..f4e4ce7d6517be 100644 --- a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs @@ -1077,6 +1077,32 @@ public static void CancellationTokenSourceWithTimer_Negative() Assert.Throws(() => { cts.CancelAfter(reasonableTimeSpan); }); } + [Fact] + public static void CancellationTokenSourceWithTimer_SupportsInfinite() + { + var cts = new CancellationTokenSource(TimeSpan.FromDays(1)); + Assert.False(cts.IsCancellationRequested); + + cts.CancelAfter(Timeout.InfiniteTimeSpan); + Assert.False(cts.IsCancellationRequested); + + Assert.True(cts.TryReset()); + + cts.CancelAfter(TimeSpan.FromDays(1)); + Assert.False(cts.IsCancellationRequested); + + cts.CancelAfter(Timeout.Infinite); + Assert.False(cts.IsCancellationRequested); + + Assert.True(cts.TryReset()); + + cts = new CancellationTokenSource(Timeout.InfiniteTimeSpan); + Assert.False(cts.IsCancellationRequested); + + cts = new CancellationTokenSource(Timeout.Infinite); + Assert.False(cts.IsCancellationRequested); + } + [Fact] public static void CancellationTokenSource_TryReset_ReturnsFalseIfAlreadyCanceled() {