// 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; namespace System.Threading { internal sealed partial class PortableThreadPool { /// <summary> /// Tracks information on the number of threads we want/have in different states in our thread pool. /// </summary> private struct ThreadCounts : IEquatable<ThreadCounts> { // SOS's ThreadPool command depends on this layout private const byte NumProcessingWorkShift = 0; private const byte NumExistingThreadsShift = 16; private const byte NumThreadsGoalShift = 32; private ulong _data; // SOS's ThreadPool command depends on this name private ThreadCounts(ulong data) => _data = data; private short GetInt16Value(byte shift) => (short)(_data >> shift); private void SetInt16Value(short value, byte shift) => _data = (_data & ~((ulong)ushort.MaxValue << shift)) | ((ulong)(ushort)value << shift); /// <summary> /// Number of threads processing work items. /// </summary> public short NumProcessingWork { get { short value = GetInt16Value(NumProcessingWorkShift); Debug.Assert(value >= 0); return value; } set { Debug.Assert(value >= 0); SetInt16Value(Math.Max((short)0, value), NumProcessingWorkShift); } } // Returns "true" if adding NumProcessingWork has reached the limit. // Note: it is possible to be in Saturated state while NumProcessingWork is under // the limit if the limit has been changed after the state was set. That is ok. // While changes in NumProcessingWork need to be matched with semaphore Wait/Signal, // the redundantly set Saturated 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 IsSaturated { 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 Saturated 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 a Saturated state, clears the Saturated state and returns false. /// Note: only if "true" is returned the NumProcessingWork is decremented. /// </summary> public bool TryDecrementProcessingWork() { Debug.Assert(NumProcessingWork > 0); if (IsSaturated) { _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> public short NumExistingThreads { get { short value = GetInt16Value(NumExistingThreadsShift); Debug.Assert(value >= 0); return value; } set { Debug.Assert(value >= 0); SetInt16Value(Math.Max((short)0, value), NumExistingThreadsShift); } } /// <summary> /// Max possible thread pool threads we want to have. /// </summary> public short NumThreadsGoal { get { short value = GetInt16Value(NumThreadsGoalShift); Debug.Assert(value > 0); return value; } set { Debug.Assert(value > 0); SetInt16Value(Math.Max((short)1, value), NumThreadsGoalShift); } } public ThreadCounts InterlockedSetNumThreadsGoal(short value) { ThreadPoolInstance._threadAdjustmentLock.VerifyIsLocked(); ThreadCounts counts = this; while (true) { ThreadCounts newCounts = counts; newCounts.NumThreadsGoal = value; ThreadCounts countsBeforeUpdate = InterlockedCompareExchange(newCounts, counts); if (countsBeforeUpdate == counts) { return newCounts; } counts = countsBeforeUpdate; } } public ThreadCounts VolatileRead() => new ThreadCounts(Volatile.Read(ref _data)); public ThreadCounts InterlockedCompareExchange(ThreadCounts newCounts, ThreadCounts oldCounts) { #if DEBUG if (newCounts.NumThreadsGoal != oldCounts.NumThreadsGoal) { ThreadPoolInstance._threadAdjustmentLock.VerifyIsLocked(); } #endif return new ThreadCounts(Interlocked.CompareExchange(ref _data, newCounts._data, oldCounts._data)); } public static bool operator ==(ThreadCounts lhs, ThreadCounts rhs) => lhs._data == rhs._data; public static bool operator !=(ThreadCounts lhs, ThreadCounts rhs) => lhs._data != rhs._data; public override bool Equals([NotNullWhen(true)] object? obj) => obj is ThreadCounts other && Equals(other); public bool Equals(ThreadCounts other) => _data == other._data; public override int GetHashCode() => (int)_data + (int)(_data >> 32); } } } |