@@ -178,7 +178,7 @@ public void Wait()
178178 if ( OperatingSystem . IsWasi ( ) ) throw new PlatformNotSupportedException ( ) ; // TODO remove with https://github.com/dotnet/runtime/pull/107185
179179#endif
180180 // Call wait with infinite timeout
181- Wait ( Timeout . Infinite , CancellationToken . None ) ;
181+ WaitCore ( Timeout . Infinite , CancellationToken . None ) ;
182182 }
183183
184184 /// <summary>
@@ -198,7 +198,7 @@ public void Wait(CancellationToken cancellationToken)
198198 if ( OperatingSystem . IsWasi ( ) ) throw new PlatformNotSupportedException ( ) ; // TODO remove with https://github.com/dotnet/runtime/pull/107185
199199#endif
200200 // Call wait with infinite timeout
201- Wait ( Timeout . Infinite , cancellationToken ) ;
201+ WaitCore ( Timeout . Infinite , cancellationToken ) ;
202202 }
203203
204204 /// <summary>
@@ -211,8 +211,7 @@ public void Wait(CancellationToken cancellationToken)
211211 /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
212212 /// otherwise, false.</returns>
213213 /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
214- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
215- /// than <see cref="int.MaxValue"/>.</exception>
214+ /// number other than -1 milliseconds, which represents an infinite time-out.</exception>
216215 [ UnsupportedOSPlatform ( "browser" ) ]
217216 public bool Wait ( TimeSpan timeout )
218217 {
@@ -221,14 +220,14 @@ public bool Wait(TimeSpan timeout)
221220#endif
222221 // Validate the timeout
223222 long totalMilliseconds = ( long ) timeout . TotalMilliseconds ;
224- if ( totalMilliseconds < - 1 || totalMilliseconds > int . MaxValue )
223+ if ( totalMilliseconds < - 1 )
225224 {
226225 throw new ArgumentOutOfRangeException (
227- nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeoutWrong ) ;
226+ nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeSpanTimeoutWrong ) ;
228227 }
229228
230229 // Call wait with the timeout milliseconds
231- return Wait ( ( int ) timeout . TotalMilliseconds , CancellationToken . None ) ;
230+ return WaitCore ( totalMilliseconds , CancellationToken . None ) ;
232231 }
233232
234233 /// <summary>
@@ -244,8 +243,7 @@ public bool Wait(TimeSpan timeout)
244243 /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>;
245244 /// otherwise, false.</returns>
246245 /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
247- /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
248- /// than <see cref="int.MaxValue"/>.</exception>
246+ /// number other than -1 milliseconds, which represents an infinite time-out.</exception>
249247 /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
250248 [ UnsupportedOSPlatform ( "browser" ) ]
251249 public bool Wait ( TimeSpan timeout , CancellationToken cancellationToken )
@@ -255,14 +253,14 @@ public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
255253#endif
256254 // Validate the timeout
257255 long totalMilliseconds = ( long ) timeout . TotalMilliseconds ;
258- if ( totalMilliseconds < - 1 || totalMilliseconds > int . MaxValue )
256+ if ( totalMilliseconds < - 1 )
259257 {
260258 throw new ArgumentOutOfRangeException (
261- nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeoutWrong ) ;
259+ nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeSpanTimeoutWrong ) ;
262260 }
263261
264262 // Call wait with the timeout milliseconds
265- return Wait ( ( int ) timeout . TotalMilliseconds , cancellationToken ) ;
263+ return WaitCore ( totalMilliseconds , cancellationToken ) ;
266264 }
267265
268266 /// <summary>
@@ -281,7 +279,7 @@ public bool Wait(int millisecondsTimeout)
281279#if TARGET_WASI
282280 if ( OperatingSystem . IsWasi ( ) ) throw new PlatformNotSupportedException ( ) ; // TODO remove with https://github.com/dotnet/runtime/pull/107185
283281#endif
284- return Wait ( millisecondsTimeout , CancellationToken . None ) ;
282+ return WaitCore ( millisecondsTimeout , CancellationToken . None ) ;
285283 }
286284
287285 /// <summary>
@@ -302,17 +300,33 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
302300#if TARGET_WASI
303301 if ( OperatingSystem . IsWasi ( ) ) throw new PlatformNotSupportedException ( ) ; // TODO remove with https://github.com/dotnet/runtime/pull/107185
304302#endif
305- CheckDispose ( ) ;
306- #if FEATURE_WASM_MANAGED_THREADS
307- Thread . AssureBlockingPossible ( ) ;
308- #endif
309303
310304 if ( millisecondsTimeout < - 1 )
311305 {
312306 throw new ArgumentOutOfRangeException (
313307 nameof ( millisecondsTimeout ) , millisecondsTimeout , SR . SemaphoreSlim_Wait_TimeoutWrong ) ;
314308 }
315309
310+ return WaitCore ( millisecondsTimeout , cancellationToken ) ;
311+ }
312+
313+ /// <summary>
314+ /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>,
315+ /// using a 32-bit unsigned integer to measure the time interval,
316+ /// while observing a <see cref="CancellationToken"/>.
317+ /// </summary>
318+ /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.UnsignedInfinite"/> to
319+ /// wait indefinitely.</param>
320+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
321+ /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; otherwise, false.</returns>
322+ /// <exception cref="OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception>
323+ [ UnsupportedOSPlatform ( "browser" ) ]
324+ private bool WaitCore ( long millisecondsTimeout , CancellationToken cancellationToken )
325+ {
326+ CheckDispose ( ) ;
327+ #if FEATURE_WASM_MANAGED_THREADS
328+ Thread . AssureBlockingPossible ( ) ;
329+ #endif
316330 cancellationToken . ThrowIfCancellationRequested ( ) ;
317331
318332 // Perf: Check the stack timeout parameter before checking the volatile count
@@ -322,10 +336,10 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
322336 return false ;
323337 }
324338
325- uint startTime = 0 ;
339+ long startTime = 0 ;
326340 if ( millisecondsTimeout != Timeout . Infinite && millisecondsTimeout > 0 )
327341 {
328- startTime = TimeoutHelper . GetTime ( ) ;
342+ startTime = Environment . TickCount64 ;
329343 }
330344
331345 bool waitSuccessful = false ;
@@ -368,7 +382,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
368382 if ( m_asyncHead is not null )
369383 {
370384 Debug . Assert ( m_asyncTail is not null , "tail should not be null if head isn't" ) ;
371- asyncWaitTask = WaitAsync ( millisecondsTimeout , cancellationToken ) ;
385+ asyncWaitTask = WaitAsyncCore ( millisecondsTimeout , cancellationToken ) ;
372386 }
373387 // There are no async waiters, so we can proceed with normal synchronous waiting.
374388 else
@@ -449,30 +463,45 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
449463 /// <param name="cancellationToken">The CancellationToken to observe.</param>
450464 /// <returns>true if the monitor received a signal, false if the timeout expired</returns>
451465 [ UnsupportedOSPlatform ( "browser" ) ]
452- private bool WaitUntilCountOrTimeout ( int millisecondsTimeout , uint startTime , CancellationToken cancellationToken )
466+ private bool WaitUntilCountOrTimeout ( long millisecondsTimeout , long startTime , CancellationToken cancellationToken )
453467 {
454468#if TARGET_WASI
455469 if ( OperatingSystem . IsWasi ( ) ) throw new PlatformNotSupportedException ( ) ; // TODO remove with https://github.com/dotnet/runtime/pull/107185
456470#endif
457- int remainingWaitMilliseconds = Timeout . Infinite ;
471+ int monitorWaitMilliseconds = Timeout . Infinite ;
458472
459473 // Wait on the monitor as long as the count is zero
460474 while ( m_currentCount == 0 )
461475 {
462476 // If cancelled, we throw. Trying to wait could lead to deadlock.
463477 cancellationToken . ThrowIfCancellationRequested ( ) ;
464478
479+ // Since Monitor.Wait will handle the actual wait and it accepts an int timeout,
480+ // we may need to cap the timeout to int.MaxValue.
481+ bool timeoutIsCapped = false ;
465482 if ( millisecondsTimeout != Timeout . Infinite )
466483 {
467- remainingWaitMilliseconds = TimeoutHelper . UpdateTimeOut ( startTime , millisecondsTimeout ) ;
484+ long remainingWaitMilliseconds = TimeoutHelper . UpdateTimeOut ( startTime , millisecondsTimeout ) ;
468485 if ( remainingWaitMilliseconds <= 0 )
469486 {
470487 // The thread has expires its timeout
471488 return false ;
472489 }
490+ if ( remainingWaitMilliseconds <= int . MaxValue )
491+ {
492+ monitorWaitMilliseconds = ( int ) remainingWaitMilliseconds ;
493+ }
494+ else
495+ {
496+ timeoutIsCapped = true ;
497+ monitorWaitMilliseconds = int . MaxValue ;
498+ }
473499 }
474- // ** the actual wait **
475- bool waitSuccessful = Monitor . Wait ( m_lockObjAndDisposed , remainingWaitMilliseconds ) ;
500+
501+
502+ // The actual wait. If the timeout was capped and waitSuccessful is false, it doesn't imply
503+ // a timeout, we are just limited by Monitor.Wait's maximum timeout value.
504+ bool waitSuccessful = Monitor . Wait ( m_lockObjAndDisposed , monitorWaitMilliseconds ) ;
476505
477506 // This waiter has woken up and this needs to be reflected in the count of waiters pulsed to wake. Since we
478507 // 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
485514 -- m_countOfWaitersPulsedToWake ;
486515 }
487516
488- if ( ! waitSuccessful )
517+ if ( ! timeoutIsCapped && ! waitSuccessful )
489518 {
490519 return false ;
491520 }
@@ -500,7 +529,7 @@ private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, Ca
500529 /// <returns>A task that will complete when the semaphore has been entered.</returns>
501530 public Task WaitAsync ( )
502531 {
503- return WaitAsync ( Timeout . Infinite , default ) ;
532+ return WaitAsyncCore ( Timeout . Infinite , default ) ;
504533 }
505534
506535 /// <summary>
@@ -516,7 +545,7 @@ public Task WaitAsync()
516545 /// </exception>
517546 public Task WaitAsync ( CancellationToken cancellationToken )
518547 {
519- return WaitAsync ( Timeout . Infinite , cancellationToken ) ;
548+ return WaitAsyncCore ( Timeout . Infinite , cancellationToken ) ;
520549 }
521550
522551 /// <summary>
@@ -537,7 +566,7 @@ public Task WaitAsync(CancellationToken cancellationToken)
537566 /// </exception>
538567 public Task < bool > WaitAsync ( int millisecondsTimeout )
539568 {
540- return WaitAsync ( millisecondsTimeout , default ) ;
569+ return WaitAsyncCore ( millisecondsTimeout , default ) ;
541570 }
542571
543572 /// <summary>
@@ -562,7 +591,16 @@ public Task<bool> WaitAsync(int millisecondsTimeout)
562591 /// </exception>
563592 public Task < bool > WaitAsync ( TimeSpan timeout )
564593 {
565- return WaitAsync ( timeout , default ) ;
594+ // Validate the timeout
595+ long totalMilliseconds = ( long ) timeout . TotalMilliseconds ;
596+ if ( totalMilliseconds < - 1 )
597+ {
598+ throw new ArgumentOutOfRangeException (
599+ nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeSpanTimeoutWrong ) ;
600+ }
601+
602+ // Call wait with the timeout milliseconds
603+ return WaitAsyncCore ( totalMilliseconds , default ) ;
566604 }
567605
568606 /// <summary>
@@ -588,14 +626,14 @@ public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToke
588626 {
589627 // Validate the timeout
590628 long totalMilliseconds = ( long ) timeout . TotalMilliseconds ;
591- if ( totalMilliseconds < - 1 || totalMilliseconds > int . MaxValue )
629+ if ( totalMilliseconds < - 1 )
592630 {
593631 throw new ArgumentOutOfRangeException (
594- nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeoutWrong ) ;
632+ nameof ( timeout ) , timeout , SR . SemaphoreSlim_Wait_TimeSpanTimeoutWrong ) ;
595633 }
596634
597635 // Call wait with the timeout milliseconds
598- return WaitAsync ( ( int ) timeout . TotalMilliseconds , cancellationToken ) ;
636+ return WaitAsyncCore ( totalMilliseconds , cancellationToken ) ;
599637 }
600638
601639 /// <summary>
@@ -618,14 +656,34 @@ public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToke
618656 /// </exception>
619657 public Task < bool > WaitAsync ( int millisecondsTimeout , CancellationToken cancellationToken )
620658 {
621- CheckDispose ( ) ;
622-
623659 if ( millisecondsTimeout < - 1 )
624660 {
625661 throw new ArgumentOutOfRangeException (
626662 nameof ( millisecondsTimeout ) , millisecondsTimeout , SR . SemaphoreSlim_Wait_TimeoutWrong ) ;
627663 }
628664
665+ return WaitAsyncCore ( millisecondsTimeout , cancellationToken ) ;
666+ }
667+
668+ /// <summary>
669+ /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>,
670+ /// using a 32-bit unsigned integer to measure the time interval,
671+ /// while observing a <see cref="CancellationToken"/>.
672+ /// </summary>
673+ /// <param name="millisecondsTimeout">
674+ /// The number of milliseconds to wait, or <see cref="Timeout.UnsignedInfinite"/> to wait indefinitely.
675+ /// </param>
676+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
677+ /// <returns>
678+ /// A task that will complete with a result of true if the current thread successfully entered
679+ /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false.
680+ /// </returns>
681+ /// <exception cref="ObjectDisposedException">The current instance has already been
682+ /// disposed.</exception>
683+ private Task < bool > WaitAsyncCore ( long millisecondsTimeout , CancellationToken cancellationToken )
684+ {
685+ CheckDispose ( ) ;
686+
629687 // Bail early for cancellation
630688 if ( cancellationToken . IsCancellationRequested )
631689 return Task . FromCanceled < bool > ( cancellationToken ) ;
@@ -716,12 +774,14 @@ private bool RemoveAsyncWaiter(TaskNode task)
716774 /// <param name="millisecondsTimeout">The timeout.</param>
717775 /// <param name="cancellationToken">The cancellation token.</param>
718776 /// <returns>The task to return to the caller.</returns>
719- private async Task < bool > WaitUntilCountOrTimeoutAsync ( TaskNode asyncWaiter , int millisecondsTimeout , CancellationToken cancellationToken )
777+ private async Task < bool > WaitUntilCountOrTimeoutAsync ( TaskNode asyncWaiter , long millisecondsTimeout , CancellationToken cancellationToken )
720778 {
721779 Debug . Assert ( asyncWaiter is not null , "Waiter should have been constructed" ) ;
722780 Debug . Assert ( Monitor . IsEntered ( m_lockObjAndDisposed ) , "Requires the lock be held" ) ;
723781
724- await ( ( Task ) asyncWaiter . WaitAsync ( TimeSpan . FromMilliseconds ( millisecondsTimeout ) , cancellationToken ) ) . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
782+ await ( ( Task ) asyncWaiter . WaitAsync (
783+ TimeSpan . FromMilliseconds ( millisecondsTimeout ) ,
784+ cancellationToken ) ) . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
725785
726786 if ( cancellationToken . IsCancellationRequested )
727787 {
0 commit comments