Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2839,6 +2839,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandleOverlapped.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolCallbackWrapper.cs" Condition="'$(FeatureNativeAot)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Backoff.cs" />
</ItemGroup>
<ItemGroup Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(FeatureWasmManagedThreads)' != 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PreAllocatedOverlapped.Browser.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace System.Threading
{
internal static class Backoff
{
// We will use exponential backoff in rare cases when we need to change state atomically and cannot
// make progress due to concurrent state changes by other threads.
// While we cannot know the ideal amount of wait needed before making a successful attempt,
// the exponential backoff will generally be not more than 2X worse than the perfect guess and
// will do a lot less attempts than a simple retry. On multiprocessor machine fruitless attempts
// will cause unnecessary sharing of the contended state which may make modifying the state more expensive.
// To protect against degenerate cases we will cap the per-iteration wait to a few thousand spinwaits.
private const uint MaxExponentialBackoffBits = 14;

internal static unsafe void Exponential(uint attempt)
{
attempt = Math.Min(attempt, MaxExponentialBackoffBits);
// We will backoff for some random number of spins that roughly grows as attempt^2
// No need for much randomness here, randomness is "good to have", we could do without it,
// so we will just cheaply hash in the stack location.
uint rand = (uint)&attempt * 2654435769u;
// Set the highmost bit to ensure minimum number of spins is exponentially increasing.
// It basically guarantees that we spin at least 0, 1, 2, 4, 8, 16, times, and so on
rand |= (1u << 31);
uint spins = rand >> (byte)(32 - attempt);
Thread.SpinWait((int)spins);
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ private uint PerformBlockingAdjustment(bool previousDelayElapsed, out bool addWo
HillClimbing.ThreadPoolHillClimber.ForceChange(
newNumThreadsGoal,
HillClimbing.StateOrTransition.CooperativeBlocking);
if (counts.NumProcessingWork >= numThreadsGoal && _separated.numRequestedWorkers > 0)

if (counts.NumProcessingWork >= numThreadsGoal && _separated._hasOutstandingThreadRequest != 0)
{
addWorker = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private static void GateThreadStart()

if (!disableStarvationDetection &&
threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None &&
threadPoolInstance._separated.numRequestedWorkers > 0 &&
threadPoolInstance._separated._hasOutstandingThreadRequest != 0 &&
SufficientDelaySinceLastDequeue(threadPoolInstance))
{
bool addWorker = false;
Expand Down Expand Up @@ -187,7 +187,7 @@ private static void GateThreadStart()
}
}

if (threadPoolInstance._separated.numRequestedWorkers <= 0 &&
if (threadPoolInstance._separated._hasOutstandingThreadRequest == 0 &&
threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None &&
Interlocked.Decrement(ref threadPoolInstance._separated.gateThreadRunningState) <= GetRunningStateForNumRuns(0))
{
Expand All @@ -208,7 +208,7 @@ public static void Wake(PortableThreadPool threadPoolInstance)
// in deciding "too long"
private static bool SufficientDelaySinceLastDequeue(PortableThreadPool threadPoolInstance)
{
uint delay = (uint)(Environment.TickCount - threadPoolInstance._separated.lastDequeueTime);
uint delay = (uint)(Environment.TickCount - threadPoolInstance._separated.lastDispatchTime);
uint minimumDelay;
if (threadPoolInstance._cpuUtilization < CpuUtilizationLow)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,65 @@ public short NumProcessingWork
}
}

// Returns "true" if adding NumProcessingWork has reached the limit.
// NOTE: it is possible to have overflow and NumProcessingWork under the limit
// at the same time if the limit has been changed afterwards. That is ok.
// While changes in NumProcessingWork need to be matched with semaphore Wait/Signal,
// the redundantly set overflow is mostly harmless and should self-correct when
// a worker that sees no work calls TryDecrementProcessingWork, possibly at a cost of
// redundant check for work.
public bool IsOverflow
{
get
{
return (long)_data < 0;
}
}

/// <summary>
/// Tries to increase the number of threads processing work items by one.
/// If at or above goal, returns false and sets overflow flag instead.
/// NOTE: only if "true" is returned the NumProcessingWork is incremented.
/// </summary>
public bool TryIncrementProcessingWork()
{
Debug.Assert(NumProcessingWork >= 0);
if (NumProcessingWork < NumThreadsGoal)
{
NumProcessingWork++;
// This should never overflow
Debug.Assert(NumProcessingWork > 0);
return true;
}
else
{
_data |= (1ul << 63);
return false;
}
}

/// <summary>
/// Tries to reduce the number of threads processing work items by one.
/// If in an overflow state, clears the overflow flag and returns false.
/// NOTE: only if "true" is returned the NumProcessingWork is decremented.
/// </summary>
public bool TryDecrementProcessingWork()
{
Debug.Assert(NumProcessingWork > 0);
if (IsOverflow)
{
_data &= ~(1ul << 63);
return false;
}
else
{
NumProcessingWork--;
// This should never underflow
Debug.Assert(NumProcessingWork >= 0);
return true;
}
}

/// <summary>
/// Number of thread pool threads that currently exist.
/// </summary>
Expand Down
Loading
Loading