diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 0fd538047d0aed..79a510f8e8e1d8 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3359,7 +3359,7 @@ The specified TaskContinuationOptions excluded all continuation kinds. - The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to Int32.MaxValue. + The value needs to translate in milliseconds to -1 (signifying an infinite timeout), 0 or a positive integer less than or equal to the maximum allowed timer duration. The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer. 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 c83625ffce15d7..9fb58d4ad3d7ab 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs @@ -151,7 +151,7 @@ internal WaitHandle WaitHandle /// /// The time span to wait before canceling this /// - /// The exception that is thrown when is less than -1 or greater than int.MaxValue. + /// The is less than -1 or greater than the maximum allowed timer duration. /// /// /// @@ -168,12 +168,12 @@ internal WaitHandle WaitHandle public CancellationTokenSource(TimeSpan delay) { long totalMilliseconds = (long)delay.TotalMilliseconds; - if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + if (totalMilliseconds < -1 || totalMilliseconds > Timer.MaxSupportedTimeout) { throw new ArgumentOutOfRangeException(nameof(delay)); } - InitializeWithTimer((int)totalMilliseconds); + InitializeWithTimer((uint)totalMilliseconds); } /// @@ -202,14 +202,14 @@ public CancellationTokenSource(int millisecondsDelay) throw new ArgumentOutOfRangeException(nameof(millisecondsDelay)); } - InitializeWithTimer(millisecondsDelay); + InitializeWithTimer((uint)millisecondsDelay); } /// /// Common initialization logic when constructing a CTS with a delay parameter. /// A zero delay will result in immediate cancellation. /// - private void InitializeWithTimer(int millisecondsDelay) + private void InitializeWithTimer(uint millisecondsDelay) { if (millisecondsDelay == 0) { @@ -218,7 +218,7 @@ private void InitializeWithTimer(int millisecondsDelay) else { _state = NotCanceledState; - _timer = new TimerQueueTimer(s_timerCallback, this, (uint)millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false); + _timer = new TimerQueueTimer(s_timerCallback, this, millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false); // The timer roots this CTS instance while it's scheduled. That is by design, so // that code like: @@ -286,8 +286,7 @@ public void Cancel(bool throwOnFirstException) /// cref="CancellationTokenSource"/> has been disposed. /// /// - /// The exception thrown when is less than -1 or - /// greater than int.MaxValue. + /// The is less than -1 or greater than maximum allowed timer duration. /// /// /// @@ -303,12 +302,12 @@ public void Cancel(bool throwOnFirstException) public void CancelAfter(TimeSpan delay) { long totalMilliseconds = (long)delay.TotalMilliseconds; - if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + if (totalMilliseconds < -1 || totalMilliseconds > Timer.MaxSupportedTimeout) { - throw new ArgumentOutOfRangeException(nameof(delay)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay); } - CancelAfter((int)totalMilliseconds); + CancelAfter((uint)totalMilliseconds); } /// @@ -337,13 +336,18 @@ public void CancelAfter(TimeSpan delay) /// public void CancelAfter(int millisecondsDelay) { - ThrowIfDisposed(); - if (millisecondsDelay < -1) { - throw new ArgumentOutOfRangeException(nameof(millisecondsDelay)); + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsDelay); } + CancelAfter((uint)millisecondsDelay); + } + + private void CancelAfter(uint millisecondsDelay) + { + ThrowIfDisposed(); + if (IsCancellationRequested) { return; @@ -379,7 +383,7 @@ public void CancelAfter(int millisecondsDelay) // the following in a try/catch block. try { - timer.Change((uint)millisecondsDelay, Timeout.UnsignedInfinite); + timer.Change(millisecondsDelay, Timeout.UnsignedInfinite); } catch (ObjectDisposedException) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index c6cbb14b0e5e42..b0b1db26739e59 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -5278,15 +5278,12 @@ public static Task Run(Func?> function, Cancella /// The time span to wait before completing the returned Task /// A Task that represents the time delay /// - /// The is less than -1 or greater than int.MaxValue. + /// The is less than -1 or greater than the maximum allowed timer duration. /// /// /// After the specified time delay, the Task is completed in RanToCompletion state. /// - public static Task Delay(TimeSpan delay) - { - return Delay(delay, default); - } + public static Task Delay(TimeSpan delay) => Delay(delay, default); /// /// Creates a Task that will complete after a time delay. @@ -5295,7 +5292,7 @@ public static Task Delay(TimeSpan delay) /// The cancellation token that will be checked prior to completing the returned Task /// A Task that represents the time delay /// - /// The is less than -1 or greater than int.MaxValue. + /// The is less than -1 or greater than the maximum allowed timer duration. /// /// /// The provided has already been disposed. @@ -5308,12 +5305,12 @@ public static Task Delay(TimeSpan delay) public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) { long totalMilliseconds = (long)delay.TotalMilliseconds; - if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + if (totalMilliseconds < -1 || totalMilliseconds > Timer.MaxSupportedTimeout) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay, ExceptionResource.Task_Delay_InvalidDelay); } - return Delay((int)totalMilliseconds, cancellationToken); + return Delay((uint)totalMilliseconds, cancellationToken); } /// @@ -5327,10 +5324,7 @@ public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) /// /// After the specified time delay, the Task is completed in RanToCompletion state. /// - public static Task Delay(int millisecondsDelay) - { - return Delay(millisecondsDelay, default); - } + public static Task Delay(int millisecondsDelay) => Delay(millisecondsDelay, default); /// /// Creates a Task that will complete after a time delay. @@ -5357,19 +5351,21 @@ public static Task Delay(int millisecondsDelay, CancellationToken cancellationTo ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsDelay, ExceptionResource.Task_Delay_InvalidMillisecondsDelay); } - return - cancellationToken.IsCancellationRequested ? FromCanceled(cancellationToken) : - millisecondsDelay == 0 ? CompletedTask : - cancellationToken.CanBeCanceled ? new DelayPromiseWithCancellation(millisecondsDelay, cancellationToken) : - new DelayPromise(millisecondsDelay); + return Delay((uint)millisecondsDelay, cancellationToken); } + private static Task Delay(uint millisecondsDelay, CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? FromCanceled(cancellationToken) : + millisecondsDelay == 0 ? CompletedTask : + cancellationToken.CanBeCanceled ? new DelayPromiseWithCancellation(millisecondsDelay, cancellationToken) : + new DelayPromise(millisecondsDelay); + /// Task that also stores the completion closure and logic for Task.Delay implementation. private class DelayPromise : Task { private readonly TimerQueueTimer? _timer; - internal DelayPromise(int millisecondsDelay) + internal DelayPromise(uint millisecondsDelay) { Debug.Assert(millisecondsDelay != 0); @@ -5379,9 +5375,9 @@ internal DelayPromise(int millisecondsDelay) if (s_asyncDebuggingEnabled) AddToActiveTasks(this); - if (millisecondsDelay != Timeout.Infinite) // no need to create the timer if it's an infinite timeout + if (millisecondsDelay != Timeout.UnsignedInfinite) // no need to create the timer if it's an infinite timeout { - _timer = new TimerQueueTimer(state => ((DelayPromise)state!).CompleteTimedOut(), this, (uint)millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false); + _timer = new TimerQueueTimer(state => ((DelayPromise)state!).CompleteTimedOut(), this, millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false); if (IsCompleted) { // Handle rare race condition where completion occurs prior to our having created and stored the timer, in which case @@ -5414,7 +5410,7 @@ private sealed class DelayPromiseWithCancellation : DelayPromise { private readonly CancellationTokenRegistration _registration; - internal DelayPromiseWithCancellation(int millisecondsDelay, CancellationToken token) : base(millisecondsDelay) + internal DelayPromiseWithCancellation(uint millisecondsDelay, CancellationToken token) : base(millisecondsDelay) { Debug.Assert(token.CanBeCanceled); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs index 6486053794a5f5..6f79993acd453c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs @@ -693,7 +693,7 @@ public ValueTask CloseAsync() public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable { - private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe; + internal const uint MaxSupportedTimeout = 0xfffffffe; private TimerHolder _timer; @@ -727,13 +727,13 @@ public Timer(TimerCallback callback, long dueTm = (long)dueTime.TotalMilliseconds; if (dueTm < -1) throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTm > MAX_SUPPORTED_TIMEOUT) + if (dueTm > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); long periodTm = (long)period.TotalMilliseconds; if (periodTm < -1) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (periodTm > MAX_SUPPORTED_TIMEOUT) + if (periodTm > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); TimerSetup(callback, state, (uint)dueTm, (uint)periodTm); @@ -757,9 +757,9 @@ public Timer(TimerCallback callback, throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); if (period < -1) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTime > MAX_SUPPORTED_TIMEOUT) + if (dueTime > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); - if (period > MAX_SUPPORTED_TIMEOUT) + if (period > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); TimerSetup(callback, state, (uint)dueTime, (uint)period); } @@ -814,9 +814,9 @@ public bool Change(long dueTime, long period) throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); if (period < -1) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); - if (dueTime > MAX_SUPPORTED_TIMEOUT) + if (dueTime > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge); - if (period > MAX_SUPPORTED_TIMEOUT) + if (period > MaxSupportedTimeout) throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge); return _timer._timer.Change((uint)dueTime, (uint)period); diff --git a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs index 26c9ae442eb349..8d4029cede1aad 100644 --- a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs @@ -1052,27 +1052,28 @@ public static void CancellationTokenSourceWithTimer() [Fact] public static void CancellationTokenSourceWithTimer_Negative() { - TimeSpan bigTimeSpan = new TimeSpan(2000, 0, 0, 0, 0); + TimeSpan bigTimeSpan = TimeSpan.FromMilliseconds(uint.MaxValue); TimeSpan reasonableTimeSpan = new TimeSpan(0, 0, 1); - // - // Test exception logic - // - Assert.Throws( - () => { new CancellationTokenSource(-2); }); - Assert.Throws( - () => { new CancellationTokenSource(bigTimeSpan); }); - CancellationTokenSource cts = new CancellationTokenSource(); - Assert.Throws( - () => { cts.CancelAfter(-2); }); - Assert.Throws( - () => { cts.CancelAfter(bigTimeSpan); }); + Assert.Throws(() => { new CancellationTokenSource(-2); }); + Assert.Throws(() => { new CancellationTokenSource(bigTimeSpan); }); + var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(uint.MaxValue - 1)); + Assert.False(cts.IsCancellationRequested); cts.Dispose(); - Assert.Throws( - () => { cts.CancelAfter(1); }); - Assert.Throws( - () => { cts.CancelAfter(reasonableTimeSpan); }); + + cts = new CancellationTokenSource(); + Assert.Throws(() => { cts.CancelAfter(-2); }); + Assert.Throws(() => { cts.CancelAfter(bigTimeSpan); }); + + cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMilliseconds(uint.MaxValue - 1)); + Assert.False(cts.IsCancellationRequested); + cts.Dispose(); + + cts.Dispose(); + Assert.Throws(() => { cts.CancelAfter(1); }); + Assert.Throws(() => { cts.CancelAfter(reasonableTimeSpan); }); } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs index 5ca7e5ad552377..b86df9ca380c80 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs @@ -495,6 +495,29 @@ public static void RunFromResult_FaultedTask() Assert.True((bool)isHandledField.GetValue(holderObject), "Expected FromException task to be observed after accessing Exception"); } + [Theory] + [InlineData(-2L)] + [InlineData((long)int.MinValue)] + [InlineData((long)uint.MaxValue)] + public static void TaskDelay_OutOfBounds_ThrowsException(long delay) + { + AssertExtensions.Throws("delay", () => { Task.Delay(TimeSpan.FromMilliseconds(delay)); }); + if (delay >= int.MinValue && delay <= int.MaxValue) + { + AssertExtensions.Throws("millisecondsDelay", () => { Task.Delay((int)delay); }); + } + } + + [Fact] + public static void TaskDelay_MaxSupported_Success() + { + var cts = new CancellationTokenSource(); + Task t = Task.Delay(TimeSpan.FromMilliseconds(uint.MaxValue - 2), cts.Token); + Assert.False(t.IsCompleted); + cts.Cancel(); + Assert.True(t.IsCanceled); + } + [Fact] public static void RunDelayTests() {