Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c42b2cd

Browse files
kouvelstephentoub
authored andcommitted
Expose SpinWait.SpinOnce(int sleep1Threshold) overload (#18204)
Expose SpinWait.SpinOnce(int sleep1Threshold) overload To allow customizing the spin count threshold for Sleep(1) usage, and to allow disabling the use of Sleep(1). API review: https://github.com/dotnet/corefx/issues/29623 Part of fix for https://github.com/dotnet/corefx/issues/29595 Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
1 parent 9edace0 commit c42b2cd

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

src/Common/src/CoreLib/System/Threading/ManualResetEventSlim.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
551551
var spinner = new SpinWait();
552552
while (spinner.Count < spinCount)
553553
{
554-
spinner.SpinOnce(SpinWait.Sleep1ThresholdForSpinBeforeWait);
554+
spinner.SpinOnce(SpinWait.Sleep1ThresholdForLongSpinBeforeWait);
555555

556556
if (IsSet)
557557
{

src/Common/src/CoreLib/System/Threading/SemaphoreSlim.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,11 @@ public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
345345
// spin, contention, etc. The usual number of spin iterations that would otherwise be used here is increased to
346346
// lessen that extra expense of doing a proper wait.
347347
int spinCount = SpinWait.SpinCountforSpinBeforeWait * 4;
348-
const int Sleep1Threshold = SpinWait.Sleep1ThresholdForSpinBeforeWait * 4;
349348

350349
var spinner = new SpinWait();
351350
while (spinner.Count < spinCount)
352351
{
353-
spinner.SpinOnce(Sleep1Threshold);
352+
spinner.SpinOnce(sleep1Threshold: -1);
354353

355354
if (m_currentCount != 0)
356355
{

src/Common/src/CoreLib/System/Threading/SpinWait.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,16 @@ public struct SpinWait
8888
/// for here.
8989
/// </remarks>
9090
internal static readonly int SpinCountforSpinBeforeWait = PlatformHelper.IsSingleProcessor ? 1 : 35;
91-
internal const int Sleep1ThresholdForSpinBeforeWait = 40; // should be greater than SpinCountforSpinBeforeWait
91+
92+
/// <summary>
93+
/// Typically, Sleep(1) should not be issued for a spin-wait before a proper wait because it is usually more beneficial
94+
/// to just issue the proper wait. For longer spin-waits (when the spin count is configurable), this value may be used as
95+
/// a threshold for issuing Sleep(1).
96+
/// </summary>
97+
/// <remarks>
98+
/// Should be greater than <see cref="SpinCountforSpinBeforeWait"/> so that Sleep(1) would not be used by default.
99+
/// </remarks>
100+
internal const int Sleep1ThresholdForLongSpinBeforeWait = 40;
92101

93102
// The number of times we've spun already.
94103
private int _count;
@@ -127,12 +136,42 @@ internal set
127136
/// </remarks>
128137
public void SpinOnce()
129138
{
130-
SpinOnce(DefaultSleep1Threshold);
139+
SpinOnceCore(DefaultSleep1Threshold);
140+
}
141+
142+
/// <summary>
143+
/// Performs a single spin.
144+
/// </summary>
145+
/// <param name="sleep1Threshold">
146+
/// A minimum spin count after which <code>Thread.Sleep(1)</code> may be used. A value of <code>-1</code> may be used to
147+
/// disable the use of <code>Thread.Sleep(1)</code>.
148+
/// </param>
149+
/// <exception cref="ArgumentOutOfRangeException">
150+
/// <paramref name="sleep1Threshold"/> is less than <code>-1</code>.
151+
/// </exception>
152+
/// <remarks>
153+
/// This is typically called in a loop, and may change in behavior based on the number of times a
154+
/// <see cref="SpinOnce"/> has been called thus far on this instance.
155+
/// </remarks>
156+
public void SpinOnce(int sleep1Threshold)
157+
{
158+
if (sleep1Threshold < -1)
159+
{
160+
throw new ArgumentOutOfRangeException(nameof(sleep1Threshold), sleep1Threshold, SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
161+
}
162+
163+
if (sleep1Threshold >= 0 && sleep1Threshold < YieldThreshold)
164+
{
165+
sleep1Threshold = YieldThreshold;
166+
}
167+
168+
SpinOnceCore(sleep1Threshold);
131169
}
132170

133-
internal void SpinOnce(int sleep1Threshold)
171+
private void SpinOnceCore(int sleep1Threshold)
134172
{
135-
Debug.Assert(sleep1Threshold >= YieldThreshold || PlatformHelper.IsSingleProcessor); // so that NextSpinWillYield behaves as requested
173+
Debug.Assert(sleep1Threshold >= -1);
174+
Debug.Assert(sleep1Threshold < 0 || sleep1Threshold >= YieldThreshold);
136175

137176
// (_count - YieldThreshold) % 2 == 0: The purpose of this check is to interleave Thread.Yield/Sleep(0) with
138177
// Thread.SpinWait. Otherwise, the following issues occur:
@@ -145,7 +184,7 @@ internal void SpinOnce(int sleep1Threshold)
145184
// contention), they may switch between one another, delaying work that can make progress.
146185
if ((
147186
_count >= YieldThreshold &&
148-
(_count >= sleep1Threshold || (_count - YieldThreshold) % 2 == 0)
187+
((_count >= sleep1Threshold && sleep1Threshold >= 0) || (_count - YieldThreshold) % 2 == 0)
149188
) ||
150189
PlatformHelper.IsSingleProcessor)
151190
{
@@ -164,7 +203,7 @@ internal void SpinOnce(int sleep1Threshold)
164203
// configured to use the (default) coarse-grained system timer.
165204
//
166205

167-
if (_count >= sleep1Threshold)
206+
if (_count >= sleep1Threshold && sleep1Threshold >= 0)
168207
{
169208
RuntimeThread.Sleep(1);
170209
}

0 commit comments

Comments
 (0)