diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 497a524ed303fa..e5d98648641517 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -3410,6 +3410,9 @@
The timeout must represent a value between -1 and Int32.MaxValue, inclusive.
+
+ The value needs to translate in milliseconds to -1 (signifying an infinite timeout), or be non-negative.
+
Non existent ParameterInfo. Position bigger than member's parameters length.
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
index 78f83a11bf66b0..54b6596d6a4c94 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs
@@ -508,7 +508,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
// We spin briefly before falling back to allocating and/or waiting on a true event.
- uint startTime = 0;
+ long startTime = 0;
bool bNeedTimeoutAdjustment = false;
int realMillisecondsTimeout = millisecondsTimeout; // this will be adjusted if necessary.
@@ -520,7 +520,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
// period of time. The timeout adjustments only take effect when and if we actually
// decide to block in the kernel below.
- startTime = TimeoutHelper.GetTime();
+ startTime = Environment.TickCount64;
bNeedTimeoutAdjustment = true;
}
@@ -558,7 +558,8 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
// update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
if (bNeedTimeoutAdjustment)
{
- realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
+ // TimeoutHelper.UpdateTimeOut returns a long but the value is capped as millisecondsTimeout is an int.
+ realMillisecondsTimeout = (int)TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
if (realMillisecondsTimeout <= 0)
return false;
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
index bab6bbd332b19a..bae72e8311816b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SemaphoreSlim.cs
@@ -178,7 +178,7 @@ public void Wait()
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
#endif
// Call wait with infinite timeout
- Wait(Timeout.Infinite, CancellationToken.None);
+ WaitCore(Timeout.Infinite, CancellationToken.None);
}
///
@@ -198,7 +198,7 @@ public void Wait(CancellationToken cancellationToken)
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
#endif
// Call wait with infinite timeout
- Wait(Timeout.Infinite, cancellationToken);
+ WaitCore(Timeout.Infinite, cancellationToken);
}
///
@@ -211,8 +211,7 @@ public void Wait(CancellationToken cancellationToken)
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than .
+ /// number other than -1 milliseconds, which represents an infinite time-out.
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout)
{
@@ -221,14 +220,14 @@ public bool Wait(TimeSpan timeout)
#endif
// Validate the timeout
long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ if (totalMilliseconds < -1)
{
throw new ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeSpanTimeoutWrong);
}
// Call wait with the timeout milliseconds
- return Wait((int)timeout.TotalMilliseconds, CancellationToken.None);
+ return WaitCore(totalMilliseconds, CancellationToken.None);
}
///
@@ -244,8 +243,7 @@ public bool Wait(TimeSpan timeout)
/// true if the current thread successfully entered the ;
/// otherwise, false.
/// is a negative
- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
- /// than .
+ /// number other than -1 milliseconds, which represents an infinite time-out.
/// was canceled.
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
@@ -255,14 +253,14 @@ public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
#endif
// Validate the timeout
long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ if (totalMilliseconds < -1)
{
throw new ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeSpanTimeoutWrong);
}
// Call wait with the timeout milliseconds
- return Wait((int)timeout.TotalMilliseconds, cancellationToken);
+ return WaitCore(totalMilliseconds, cancellationToken);
}
///
@@ -281,7 +279,7 @@ public bool Wait(int millisecondsTimeout)
#if TARGET_WASI
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
#endif
- return Wait(millisecondsTimeout, CancellationToken.None);
+ return WaitCore(millisecondsTimeout, CancellationToken.None);
}
///
@@ -302,10 +300,6 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
#if TARGET_WASI
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
#endif
- CheckDispose();
-#if FEATURE_WASM_MANAGED_THREADS
- Thread.AssureBlockingPossible();
-#endif
if (millisecondsTimeout < -1)
{
@@ -313,6 +307,26 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
nameof(millisecondsTimeout), millisecondsTimeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
}
+ return WaitCore(millisecondsTimeout, cancellationToken);
+ }
+
+ ///
+ /// Blocks the current thread until it can enter the ,
+ /// using a 32-bit unsigned integer to measure the time interval,
+ /// while observing a .
+ ///
+ /// The number of milliseconds to wait, or to
+ /// wait indefinitely.
+ /// The to observe.
+ /// true if the current thread successfully entered the ; otherwise, false.
+ /// was canceled.
+ [UnsupportedOSPlatform("browser")]
+ private bool WaitCore(long millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ CheckDispose();
+#if FEATURE_WASM_MANAGED_THREADS
+ Thread.AssureBlockingPossible();
+#endif
cancellationToken.ThrowIfCancellationRequested();
// Perf: Check the stack timeout parameter before checking the volatile count
@@ -322,10 +336,10 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
return false;
}
- uint startTime = 0;
+ long startTime = 0;
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0)
{
- startTime = TimeoutHelper.GetTime();
+ startTime = Environment.TickCount64;
}
bool waitSuccessful = false;
@@ -368,7 +382,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
if (m_asyncHead is not null)
{
Debug.Assert(m_asyncTail is not null, "tail should not be null if head isn't");
- asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken);
+ asyncWaitTask = WaitAsyncCore(millisecondsTimeout, cancellationToken);
}
// There are no async waiters, so we can proceed with normal synchronous waiting.
else
@@ -449,12 +463,12 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
/// The CancellationToken to observe.
/// true if the monitor received a signal, false if the timeout expired
[UnsupportedOSPlatform("browser")]
- private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, CancellationToken cancellationToken)
+ private bool WaitUntilCountOrTimeout(long millisecondsTimeout, long startTime, CancellationToken cancellationToken)
{
#if TARGET_WASI
if (OperatingSystem.IsWasi()) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185
#endif
- int remainingWaitMilliseconds = Timeout.Infinite;
+ int monitorWaitMilliseconds = Timeout.Infinite;
// Wait on the monitor as long as the count is zero
while (m_currentCount == 0)
@@ -462,17 +476,32 @@ private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, Ca
// If cancelled, we throw. Trying to wait could lead to deadlock.
cancellationToken.ThrowIfCancellationRequested();
+ // Since Monitor.Wait will handle the actual wait and it accepts an int timeout,
+ // we may need to cap the timeout to int.MaxValue.
+ bool timeoutIsCapped = false;
if (millisecondsTimeout != Timeout.Infinite)
{
- remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
+ long remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
if (remainingWaitMilliseconds <= 0)
{
// The thread has expires its timeout
return false;
}
+ if (remainingWaitMilliseconds <= int.MaxValue)
+ {
+ monitorWaitMilliseconds = (int)remainingWaitMilliseconds;
+ }
+ else
+ {
+ timeoutIsCapped = true;
+ monitorWaitMilliseconds = int.MaxValue;
+ }
}
- // ** the actual wait **
- bool waitSuccessful = Monitor.Wait(m_lockObjAndDisposed, remainingWaitMilliseconds);
+
+
+ // The actual wait. If the timeout was capped and waitSuccessful is false, it doesn't imply
+ // a timeout, we are just limited by Monitor.Wait's maximum timeout value.
+ bool waitSuccessful = Monitor.Wait(m_lockObjAndDisposed, monitorWaitMilliseconds);
// This waiter has woken up and this needs to be reflected in the count of waiters pulsed to wake. Since we
// don't have thread-specific pulse state, there is not enough information to tell whether this thread woke up
@@ -485,7 +514,7 @@ private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, Ca
--m_countOfWaitersPulsedToWake;
}
- if (!waitSuccessful)
+ if (!timeoutIsCapped && !waitSuccessful)
{
return false;
}
@@ -500,7 +529,7 @@ private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, Ca
/// A task that will complete when the semaphore has been entered.
public Task WaitAsync()
{
- return WaitAsync(Timeout.Infinite, default);
+ return WaitAsyncCore(Timeout.Infinite, default);
}
///
@@ -516,7 +545,7 @@ public Task WaitAsync()
///
public Task WaitAsync(CancellationToken cancellationToken)
{
- return WaitAsync(Timeout.Infinite, cancellationToken);
+ return WaitAsyncCore(Timeout.Infinite, cancellationToken);
}
///
@@ -537,7 +566,7 @@ public Task WaitAsync(CancellationToken cancellationToken)
///
public Task WaitAsync(int millisecondsTimeout)
{
- return WaitAsync(millisecondsTimeout, default);
+ return WaitAsyncCore(millisecondsTimeout, default);
}
///
@@ -562,7 +591,16 @@ public Task WaitAsync(int millisecondsTimeout)
///
public Task WaitAsync(TimeSpan timeout)
{
- return WaitAsync(timeout, default);
+ // Validate the timeout
+ long totalMilliseconds = (long)timeout.TotalMilliseconds;
+ if (totalMilliseconds < -1)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeSpanTimeoutWrong);
+ }
+
+ // Call wait with the timeout milliseconds
+ return WaitAsyncCore(totalMilliseconds, default);
}
///
@@ -588,14 +626,14 @@ public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToke
{
// Validate the timeout
long totalMilliseconds = (long)timeout.TotalMilliseconds;
- if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
+ if (totalMilliseconds < -1)
{
throw new ArgumentOutOfRangeException(
- nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
+ nameof(timeout), timeout, SR.SemaphoreSlim_Wait_TimeSpanTimeoutWrong);
}
// Call wait with the timeout milliseconds
- return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken);
+ return WaitAsyncCore(totalMilliseconds, cancellationToken);
}
///
@@ -618,14 +656,34 @@ public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToke
///
public Task WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken)
{
- CheckDispose();
-
if (millisecondsTimeout < -1)
{
throw new ArgumentOutOfRangeException(
nameof(millisecondsTimeout), millisecondsTimeout, SR.SemaphoreSlim_Wait_TimeoutWrong);
}
+ return WaitAsyncCore(millisecondsTimeout, cancellationToken);
+ }
+
+ ///
+ /// Asynchronously waits to enter the ,
+ /// using a 32-bit unsigned integer to measure the time interval,
+ /// while observing a .
+ ///
+ ///
+ /// The number of milliseconds to wait, or to wait indefinitely.
+ ///
+ /// The to observe.
+ ///
+ /// A task that will complete with a result of true if the current thread successfully entered
+ /// the , otherwise with a result of false.
+ ///
+ /// The current instance has already been
+ /// disposed.
+ private Task WaitAsyncCore(long millisecondsTimeout, CancellationToken cancellationToken)
+ {
+ CheckDispose();
+
// Bail early for cancellation
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
@@ -716,12 +774,14 @@ private bool RemoveAsyncWaiter(TaskNode task)
/// The timeout.
/// The cancellation token.
/// The task to return to the caller.
- private async Task WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken)
+ private async Task WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, long millisecondsTimeout, CancellationToken cancellationToken)
{
Debug.Assert(asyncWaiter is not null, "Waiter should have been constructed");
Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed), "Requires the lock be held");
- await ((Task)asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
+ await ((Task)asyncWaiter.WaitAsync(
+ TimeSpan.FromMilliseconds(millisecondsTimeout),
+ cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
if (cancellationToken.IsCancellationRequested)
{
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinLock.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinLock.cs
index c0eba40cb428af..d50709ce2e5fc5 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinLock.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinLock.cs
@@ -287,10 +287,10 @@ private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinLock_TryEnter_ArgumentOutOfRange);
}
- uint startTime = 0;
+ long startTime = 0;
if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
{
- startTime = TimeoutHelper.GetTime();
+ startTime = Environment.TickCount64;
}
if (IsThreadOwnerTrackingEnabled)
@@ -404,7 +404,7 @@ private void DecrementWaiters()
///
/// ContinueTryEnter for the thread tracking mode enabled
///
- private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
+ private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, long startTime, ref bool lockTaken)
{
Debug.Assert(IsThreadOwnerTrackingEnabled);
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
index 1922f67f93d04c..4246a33191d10b 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/SpinWait.cs
@@ -315,7 +315,7 @@ public static bool SpinUntil(Func condition, int millisecondsTimeout)
uint startTime = 0;
if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
{
- startTime = TimeoutHelper.GetTime();
+ startTime = (uint)Environment.TickCount;
}
SpinWait spinner = default;
while (!condition())
@@ -329,7 +329,7 @@ public static bool SpinUntil(Func condition, int millisecondsTimeout)
if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield)
{
- if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
+ if (millisecondsTimeout <= (uint)Environment.TickCount - startTime)
{
return false;
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/TimeoutHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/TimeoutHelper.cs
index b17d38d44d7131..37500c4dfa5e82 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/TimeoutHelper.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/TimeoutHelper.cs
@@ -6,42 +6,29 @@
namespace System.Threading
{
///
- /// A helper class to capture a start time using as a time in milliseconds.
- /// Also updates a given timeout by subtracting the current time from the start time.
+ /// A helper class to update a given timeout by subtracting the current time from the start time.
///
internal static class TimeoutHelper
{
- ///
- /// Returns as a start time in milliseconds as a .
- /// rolls over from positive to negative every ~25 days, then ~25 days to back to positive again.
- /// is used to ignore the sign and double the range to 50 days.
- ///
- public static uint GetTime()
- {
- return (uint)Environment.TickCount;
- }
-
///
/// Helper function to measure and update the elapsed time
///
/// The first time (in milliseconds) observed when the wait started
/// The original wait timeout in milliseconds
- /// The new wait time in milliseconds, or -1 if the time expired
- public static int UpdateTimeOut(uint startTime, int originalWaitMillisecondsTimeout)
+ /// The new wait time in milliseconds
+ public static long UpdateTimeOut(long startTime, long originalWaitMillisecondsTimeout)
{
// The function must be called in case the time out is not infinite
Debug.Assert(originalWaitMillisecondsTimeout != Timeout.Infinite);
- uint elapsedMilliseconds = (GetTime() - startTime);
+ ulong elapsedMilliseconds = (ulong)(Environment.TickCount64 - startTime);
- // Check the elapsed milliseconds is greater than max int because this property is uint
- if (elapsedMilliseconds > int.MaxValue)
+ if (elapsedMilliseconds > long.MaxValue)
{
return 0;
}
- // Subtract the elapsed time from the current wait time
- int currentWaitTimeout = originalWaitMillisecondsTimeout - (int)elapsedMilliseconds;
+ long currentWaitTimeout = originalWaitMillisecondsTimeout - (long)elapsedMilliseconds;
if (currentWaitTimeout <= 0)
{
return 0;
diff --git a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs
index faac4d9cf9f85b..7bdc0fdb60500a 100644
--- a/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs
+++ b/src/libraries/System.Threading/tests/SemaphoreSlimTests.cs
@@ -60,6 +60,7 @@ public static void RunSemaphoreSlimTest1_Wait()
RunSemaphoreSlimTest1_Wait_Helper(10, 10, 10, true, null);
RunSemaphoreSlimTest1_Wait_Helper(1, 10, 10, true, null);
RunSemaphoreSlimTest1_Wait_Helper(0, 10, 10, false, null);
+ RunSemaphoreSlimTest1_Wait_Helper(1, 10, TimeSpan.FromMilliseconds(uint.MaxValue), true, null);
}
[Fact]
@@ -67,8 +68,6 @@ public static void RunSemaphoreSlimTest1_Wait_NegativeCases()
{
// Invalid timeout
RunSemaphoreSlimTest1_Wait_Helper(10, 10, -10, true, typeof(ArgumentOutOfRangeException));
- RunSemaphoreSlimTest1_Wait_Helper
- (10, 10, new TimeSpan(0, 0, int.MaxValue), true, typeof(ArgumentOutOfRangeException));
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -87,6 +86,8 @@ public static void RunSemaphoreSlimTest1_WaitAsync()
RunSemaphoreSlimTest1_WaitAsync_Helper(10, 10, 10, true, null);
RunSemaphoreSlimTest1_WaitAsync_Helper(1, 10, 10, true, null);
RunSemaphoreSlimTest1_WaitAsync_Helper(0, 10, 10, false, null);
+ RunSemaphoreSlimTest1_WaitAsync_Helper(1, 10, TimeSpan.FromMilliseconds(uint.MaxValue), true, null);
+ RunSemaphoreSlimTest1_WaitAsync_Helper(1, 10, TimeSpan.MaxValue, true, null);
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -95,8 +96,6 @@ public static void RunSemaphoreSlimTest1_WaitAsync_NegativeCases()
{
// Invalid timeout
RunSemaphoreSlimTest1_WaitAsync_Helper(10, 10, -10, true, typeof(ArgumentOutOfRangeException));
- RunSemaphoreSlimTest1_WaitAsync_Helper
- (10, 10, new TimeSpan(0, 0, int.MaxValue), true, typeof(ArgumentOutOfRangeException));
RunSemaphoreSlimTest1_WaitAsync2();
}