@@ -15,12 +15,19 @@ namespace System.Threading
1515 /// </summary>
1616 internal sealed partial class LowLevelLifoSemaphore
1717 {
18+ // The spin count is chosen to be in the range of typical thread wake latency and some additional overhead,
19+ // all assuming a single spin is calibrated to around 35 nanoseconds.
20+ // The thread wake latency commonly measures at 2-10 microsecond (year 2026) and unlikely to drastically change.
1821 private const int DefaultSemaphoreSpinCountLimit = 256 ;
22+ // The cooldown roughly serves as detection that the thread did not spend time being blocked.
23+ // If it woke in under 4 microseconds, it was likely a fast/trivial wake without blocking.
24+ private const int DefaultWakeCooldown = 4 ;
1925
2026 private CacheLineSeparatedCounts _separated ;
2127
2228 private readonly int _maximumSignalCount ;
23- private readonly int _maxSpinCount ;
29+ private readonly short _maxSpinCount ;
30+ private readonly short _threadWakeCooldownUsec ;
2431 private readonly Action _onWait ;
2532
2633 // When we need to block threads we use a linked list of per-thread blockers.
@@ -60,16 +67,17 @@ public LowLevelLifoSemaphore(int maximumSignalCount, Action onWait)
6067 _maximumSignalCount = maximumSignalCount ;
6168 _onWait = onWait ;
6269
63- _maxSpinCount = AppContextConfigHelper . GetInt32ComPlusOrDotNetConfig (
70+ _maxSpinCount = AppContextConfigHelper . GetInt16ComPlusOrDotNetConfig (
6471 "System.Threading.ThreadPool.UnfairSemaphoreSpinLimit" ,
6572 "ThreadPool_UnfairSemaphoreSpinLimit" ,
6673 DefaultSemaphoreSpinCountLimit ,
6774 false ) ;
6875
69- // Do not accept unreasonably huge _maxSpinCount value to prevent overflows.
70- // Also, 1+ minute spins do not make sense.
71- if ( _maxSpinCount > 1000000 )
72- _maxSpinCount = DefaultSemaphoreSpinCountLimit ;
76+ _threadWakeCooldownUsec = AppContextConfigHelper . GetInt16ComPlusOrDotNetConfig (
77+ "System.Threading.ThreadPool.UnfairSemaphoreWakeCooldown" ,
78+ "ThreadPool_UnfairSemaphoreWakeCooldown" ,
79+ DefaultWakeCooldown ,
80+ false ) ;
7381 }
7482
7583 public bool Wait ( int timeoutMs )
@@ -214,7 +222,7 @@ private bool WaitAsWaiter(int timeoutMs)
214222 // and are hard to avoid completely.
215223 // So, if a fast wake happened when parking was desired, we hold up the thread a bit
216224 // before releasing.
217- long cooldown = Stopwatch . Frequency * 4 / 1000000 ;
225+ long cooldown = Stopwatch . Frequency * _threadWakeCooldownUsec / 1000000 ;
218226 while ( Stopwatch . GetTimestamp ( ) - waitStartTick < cooldown )
219227 {
220228 Thread . UninterruptibleSleep0 ( ) ;
0 commit comments