Description
If code creates a CancellationTokenSource with an infinite delay, when that code is invoked with any TimeProvider instead of TimeProvider.System, an ArgumentOutOfRangeException is thrown.
I'm guessing this is caused by the code here that casts the long to a uint wrapping around, which takes the value 1 beyond the maximum value in the validation code for Timer.
|
public CancellationTokenSource(TimeSpan delay, TimeProvider timeProvider) |
|
{ |
|
ArgumentNullException.ThrowIfNull(timeProvider); |
|
long totalMilliseconds = (long)delay.TotalMilliseconds; |
|
if (totalMilliseconds < -1 || totalMilliseconds > Timer.MaxSupportedTimeout) |
|
{ |
|
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay); |
|
} |
|
|
|
InitializeWithTimer((uint)totalMilliseconds, timeProvider); |
|
} |
|
internal const uint MaxSupportedTimeout = 0xfffffffe; |
Reproduction Steps
Execute the following xunit test targeting net8.0:
using Microsoft.Extensions.Time.Testing;
namespace CancellationTokenSourceWithTimeProviderRepro;
public static class CancellationTokenSourceTests
{
public static IEnumerable<object[]> TimeProviders => new[]
{
new[] { TimeProvider.System },
new[] { new DerivedTimeProvider() },
new[] { new FakeTimeProvider() },
};
[Theory]
[MemberData(nameof(TimeProviders))]
public static void Can_Create_Infinite_CancellationTokenSource_With_TimeProvider(TimeProvider timeProvider)
{
using var cts = new CancellationTokenSource(Timeout.InfiniteTimeSpan, timeProvider);
Assert.False(cts.IsCancellationRequested);
}
private sealed class DerivedTimeProvider : TimeProvider
{
// Changes no behaviour, but is not the same underlying type/object as TimeProvider.System
}
}
Expected behavior
All three tests pass.
Actual behavior
The tests that do not use TimeProvider.System both fail with similar exceptions:
Can Create Infinite CancellationTokenSource With TimeProvider(timeProvider: DerivedTimeProvider { LocalTimeZone = (UTC+00:00) Dublin, Edinburgh, Lisbon, London, TimestampFrequency = 10000000 })
Duration: 1 ms
Message:
System.ArgumentOutOfRangeException : dueTime ('4294967295') must be less than or equal to '4294967294'. (Parameter 'dueTime')
Actual value was 4294967295.
Stack Trace:
ArgumentOutOfRangeException.ThrowGreater[T](String paramName, T value, T other)
SystemTimeProviderTimer.CheckAndGetValues(TimeSpan dueTime, TimeSpan periodTime)
SystemTimeProviderTimer.ctor(TimeSpan dueTime, TimeSpan period, TimerCallback callback, Object state)
TimeProvider.CreateTimer(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period)
CancellationTokenSource.InitializeWithTimer(UInt32 millisecondsDelay, TimeProvider timeProvider)
CancellationTokenSource.ctor(TimeSpan delay, TimeProvider timeProvider)
CancellationTokenSourceTests.Can_Create_Infinite_CancellationTokenSource_With_TimeProvider(TimeProvider timeProvider) line 21
InvokeStub_CancellationTokenSourceTests.Can_Create_Infinite_CancellationTokenSource_With_TimeProvider(Object, Object, IntPtr*)
MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
Can Create Infinite CancellationTokenSource With TimeProvider(timeProvider: 2000-01-01T00:00:00.000)
Duration: 1 ms
Message:
System.ArgumentOutOfRangeException : Argument not in the range [-1..4294967294] (Parameter 'dueTime')
Actual value was 4294967295.
Stack Trace:
Throw.ArgumentOutOfRangeException(String paramName, Object actualValue, String message)
Waiter.ChangeAndValidateDurations(TimeSpan dueTime, TimeSpan period)
Waiter.ctor(FakeTimeProvider fakeTimeProvider, TimeSpan dueTime, TimeSpan period, TimerCallback callback, Object state)
FakeTimeProviderTimer.ctor(FakeTimeProvider fakeTimeProvider, TimeSpan dueTime, TimeSpan period, TimerCallback callback, Object state)
FakeTimeProvider.CreateTimer(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period)
CancellationTokenSource.InitializeWithTimer(UInt32 millisecondsDelay, TimeProvider timeProvider)
CancellationTokenSource.ctor(TimeSpan delay, TimeProvider timeProvider)
CancellationTokenSourceTests.Can_Create_Infinite_CancellationTokenSource_With_TimeProvider(TimeProvider timeProvider) line 21
InvokeStub_CancellationTokenSourceTests.Can_Create_Infinite_CancellationTokenSource_With_TimeProvider(Object, Object, IntPtr*)
MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
Regression?
Unknown.
Known Workarounds
None.
Configuration
- .NET SDK
8.0.100-preview.5.23303.2
- Microsoft.Extensions.TimeProvider.Testing
8.0.0-preview.5.23308.3
Other information
No response
Description
If code creates a
CancellationTokenSourcewith an infinite delay, when that code is invoked with anyTimeProviderinstead ofTimeProvider.System, anArgumentOutOfRangeExceptionis thrown.I'm guessing this is caused by the code here that casts the
longto auintwrapping around, which takes the value 1 beyond the maximum value in the validation code forTimer.runtime/src/libraries/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs
Lines 155 to 165 in cfe30c0
runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs
Line 824 in cfe30c0
Reproduction Steps
Execute the following xunit test targeting
net8.0:Expected behavior
All three tests pass.
Actual behavior
The tests that do not use
TimeProvider.Systemboth fail with similar exceptions:Regression?
Unknown.
Known Workarounds
None.
Configuration
8.0.100-preview.5.23303.28.0.0-preview.5.23308.3Other information
No response