| File: System\Threading\ManagedThreadId.cs | Web Access |
| Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // Thread tracks managed thread IDs, recycling them when threads die to keep the set of // live IDs compact. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Diagnostics; namespace System.Threading { internal class ManagedThreadId { // // Binary tree used to keep track of active thread ids. Each node of the tree keeps track of 32 consecutive ids. // Implemented as immutable collection to avoid locks. Each modification creates a new top level node. // private class ImmutableIdDispenser { private readonly ImmutableIdDispenser? _left; // Child nodes private readonly ImmutableIdDispenser? _right; private readonly int _used; // Number of ids tracked by this node and all its childs private readonly int _size; // Maximum number of ids that can be tracked by this node and all its childs private readonly uint _bitmap; // Bitmap of ids tracked by this node private const int BitsPerNode = 32; private ImmutableIdDispenser(ImmutableIdDispenser? left, ImmutableIdDispenser? right, int used, int size, uint bitmap) { _left = left; _right = right; _used = used; _size = size; _bitmap = bitmap; CheckInvariants(); } [Conditional("DEBUG")] private void CheckInvariants() { int actualUsed = 0; uint countBits = _bitmap; while (countBits != 0) { actualUsed += (int)(countBits & 1); countBits >>= 1; } if (_left != null) { Debug.Assert(_left._size == ChildSize); actualUsed += _left._used; } if (_right != null) { Debug.Assert(_right._size == ChildSize); actualUsed += _right._used; } Debug.Assert(actualUsed == _used); Debug.Assert(_used <= _size); } private int ChildSize { get { Debug.Assert((_size / 2) >= (BitsPerNode / 2)); return (_size / 2) - (BitsPerNode / 2); } } public static ImmutableIdDispenser Empty { get { // The empty dispenser has the id=0 allocated, so it is not really empty. // It saves us from dealing with the corner case of true empty dispenser, // and it ensures that IdNone will not be ever given out. return new ImmutableIdDispenser(null, null, 1, BitsPerNode, 1); } } public ImmutableIdDispenser AllocateId(out int id) { if (_used == _size) { id = _size; return new ImmutableIdDispenser(this, null, _size + 1, checked(2 * _size + BitsPerNode), 1); } var bitmap = _bitmap; var left = _left; var right = _right; // Any free bits in current node? if (bitmap != uint.MaxValue) { int bit = 0; while ((bitmap & (uint)(1 << bit)) != 0) bit++; bitmap |= (uint)(1 << bit); id = ChildSize + bit; } else { Debug.Assert(ChildSize > 0); if (left == null) { left = new ImmutableIdDispenser(null, null, 1, ChildSize, 1); id = left.ChildSize; } else if (right == null) { right = new ImmutableIdDispenser(null, null, 1, ChildSize, 1); id = ChildSize + BitsPerNode + right.ChildSize; } else { if (left._used < right._used) { Debug.Assert(left._used < left._size); left = left.AllocateId(out id); } else { Debug.Assert(right._used < right._size); right = right.AllocateId(out id); id += (ChildSize + BitsPerNode); } } } return new ImmutableIdDispenser(left, right, _used + 1, _size, bitmap); } public ImmutableIdDispenser? RecycleId(int id) { Debug.Assert(id < _size); if (_used == 1) return null; var bitmap = _bitmap; var left = _left; var right = _right; int childSize = ChildSize; if (id < childSize) { left = left.RecycleId(id); } else { id -= childSize; if (id < BitsPerNode) { Debug.Assert((bitmap & (uint)(1 << id)) != 0); bitmap &= ~(uint)(1 << id); } else { right = right.RecycleId(id - BitsPerNode); } } return new ImmutableIdDispenser(left, right, _used - 1, _size, bitmap); } } public const int IdNone = 0; // The main thread takes the first available id, which is 1. This id will not be recycled until the process exit. // We use this id to detect the main thread and report it as a foreground one. public const int IdMainThread = 1; // We store ManagedThreadId both here and in the Thread.CurrentThread object. We store it here, // because we may need the id very early in the process lifetime (e.g., in ClassConstructorRunner), // when a Thread object cannot be created yet. We also store it in the Thread.CurrentThread object, // because that object may have longer lifetime than the OS thread. [ThreadStatic] private static ManagedThreadId t_currentThreadId; [ThreadStatic] private static int t_currentManagedThreadId; // We have to avoid the static constructors on the ManagedThreadId class, otherwise we can run into stack overflow as first time Current property get called, // the runtime will ensure running the static constructor and this process will call the Current property again (when taking any lock) // System::Environment.get_CurrentManagedThreadId // System::Threading::Lock.Acquire // System::Runtime::CompilerServices::ClassConstructorRunner::Cctor.GetCctor // System::Runtime::CompilerServices::ClassConstructorRunner.EnsureClassConstructorRun // System::Threading::ManagedThreadId.get_Current // System::Environment.get_CurrentManagedThreadId private static ImmutableIdDispenser? s_idDispenser; private int _managedThreadId; public int Id => _managedThreadId; public static int AllocateId() { if (s_idDispenser == null) Interlocked.CompareExchange(ref s_idDispenser, ImmutableIdDispenser.Empty, null); Debug.Assert(s_idDispenser != null); int id; var priorIdDispenser = Volatile.Read(ref s_idDispenser); for (; ; ) { var updatedIdDispenser = priorIdDispenser.AllocateId(out id); var interlockedResult = Interlocked.CompareExchange(ref s_idDispenser, updatedIdDispenser, priorIdDispenser); if (object.ReferenceEquals(priorIdDispenser, interlockedResult)) break; priorIdDispenser = interlockedResult; // we already have a volatile read that we can reuse for the next loop } Debug.Assert(id != IdNone); return id; } public static void RecycleId(int id) { if (id == IdNone) { return; } var priorIdDispenser = Volatile.Read(ref s_idDispenser); for (; ; ) { var updatedIdDispenser = s_idDispenser.RecycleId(id); var interlockedResult = Interlocked.CompareExchange(ref s_idDispenser, updatedIdDispenser, priorIdDispenser); if (object.ReferenceEquals(priorIdDispenser, interlockedResult)) break; priorIdDispenser = interlockedResult; // we already have a volatile read that we can reuse for the next loop } } internal static int CurrentManagedThreadIdUnchecked => t_currentManagedThreadId; public static int Current { get { int currentManagedThreadId = t_currentManagedThreadId; if (currentManagedThreadId == IdNone) return MakeForCurrentThread(); else return currentManagedThreadId; } } public static ManagedThreadId GetCurrentThreadId() { if (t_currentManagedThreadId == IdNone) MakeForCurrentThread(); return t_currentThreadId; } private static int MakeForCurrentThread() { return SetForCurrentThread(new ManagedThreadId()); } public static int SetForCurrentThread(ManagedThreadId threadId) { t_currentThreadId = threadId; t_currentManagedThreadId = threadId.Id; return threadId.Id; } public ManagedThreadId() { _managedThreadId = AllocateId(); } ~ManagedThreadId() { RecycleId(_managedThreadId); } } }