| File: System\Threading\Thread.NativeAot.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. using System.Diagnostics; using System.Globalization; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; namespace System.Threading { [DataContract] public sealed partial class Thread { // Extra bits used in _threadState private const ThreadState ThreadPoolThread = (ThreadState)0x1000; #if TARGET_WINDOWS private const ThreadState Interrupted = (ThreadState)0x2000; #endif // Bits of _threadState that are returned by the ThreadState property private const ThreadState PublicThreadStateMask = (ThreadState)0x1FF; internal ExecutionContext? _executionContext; internal SynchronizationContext? _synchronizationContext; private volatile int _threadState = (int)ThreadState.Unstarted; private ThreadPriority _priority; [DataContract] private ManagedThreadId _managedThreadId; [DataContract] private string? _name; private StartHelper? _startHelper; private Exception? _startException; // Protects starting the thread and setting its priority private Lock _lock = new Lock(useTrivialWaits: true); // This is used for a quick check on thread pool threads after running a work item to determine if the name, background // state, or priority were changed by the work item, and if so to reset it. Other threads may also change some of those, // but those types of changes may race with the reset anyway, so this field doesn't need to be synchronized. private bool _mayNeedResetForThreadPool; // so far the only place we initialize it is `WaitForForegroundThreads` // and only in the case when there are running foreground threads // by the moment of `StartupCodeHelpers.Shutdown()` invocation private static ManualResetEvent s_allDone; private static int s_foregroundRunningCount; private Thread() { _managedThreadId = System.Threading.ManagedThreadId.GetCurrentThreadId(); PlatformSpecificInitialize(); RegisterThreadExitCallback(); } private void Initialize() { _priority = ThreadPriority.Normal; _managedThreadId = new ManagedThreadId(); PlatformSpecificInitialize(); RegisterThreadExitCallback(); } private static unsafe void RegisterThreadExitCallback() { RuntimeImports.RhSetThreadExitCallback(&OnThreadExit); } internal static ulong CurrentOSThreadId { get { return RuntimeImports.RhCurrentOSThreadId(); } } // Slow path executed once per thread [MethodImpl(MethodImplOptions.NoInlining)] private static Thread InitializeCurrentThread() { Debug.Assert(t_currentThread == null); var currentThread = new Thread(); Debug.Assert(currentThread._threadState == (int)ThreadState.Unstarted); ThreadState state = 0; // The main thread is foreground, other ones are background if (currentThread._managedThreadId.Id != System.Threading.ManagedThreadId.IdMainThread) { state |= ThreadState.Background; } else { Interlocked.Increment(ref s_foregroundRunningCount); } currentThread._threadState = (int)(state | ThreadState.Running); currentThread.PlatformSpecificInitializeExistingThread(); currentThread._priority = currentThread.GetPriorityLive(); t_currentThread = currentThread; return currentThread; } /// <summary> /// Returns true if the underlying OS thread has been created and started execution of managed code. /// </summary> private bool HasStarted() { return !GetThreadStateBit(ThreadState.Unstarted); } public bool IsAlive { get { return ((ThreadState)_threadState & (ThreadState.Unstarted | ThreadState.Stopped | ThreadState.Aborted)) == 0; } } private bool IsDead() { return ((ThreadState)_threadState & (ThreadState.Stopped | ThreadState.Aborted)) != 0; } public bool IsBackground { get { if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_State); } return GetThreadStateBit(ThreadState.Background); } set { if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_State); } // we changing foreground count only for started threads // on thread start we count its state in `StartThread` if (value) { int threadState = SetThreadStateBit(ThreadState.Background); // was foreground and has started if ((threadState & ((int)ThreadState.Background | (int)ThreadState.Unstarted | (int)ThreadState.Stopped)) == 0) { DecrementRunningForeground(); } } else { int threadState = ClearThreadStateBit(ThreadState.Background); // was background and has started if ((threadState & ((int)ThreadState.Background | (int)ThreadState.Unstarted | (int)ThreadState.Stopped)) == (int)ThreadState.Background) { IncrementRunningForeground(); _mayNeedResetForThreadPool = true; } } } } public bool IsThreadPoolThread { get { if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_State); } return GetThreadStateBit(ThreadPoolThread); } internal set { if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_State); } if (value) { SetThreadStateBit(ThreadPoolThread); } else { ClearThreadStateBit(ThreadPoolThread); } } } public int ManagedThreadId { [Intrinsic] get => _managedThreadId.Id; } // TODO: Support non-current thread private void ThreadNameChanged(string? value) { if (Thread.CurrentThread != this) { return; } if (value == null) { return; } RuntimeImports.RhSetCurrentThreadName(value); } public ThreadPriority Priority { get { if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_Priority); } if (!HasStarted()) { // The thread has not been started yet; return the value assigned to the Priority property. // Race condition with setting the priority or starting the thread is OK, we may return an old value. return _priority; } // The priority might have been changed by external means. Obtain the actual value from the OS // rather than using the value saved in _priority. return GetPriorityLive(); } set { if ((value < ThreadPriority.Lowest) || (ThreadPriority.Highest < value)) { throw new ArgumentOutOfRangeException(SR.Argument_InvalidFlag); } if (IsDead()) { throw new ThreadStateException(SR.ThreadState_Dead_Priority); } // Prevent race condition with starting this thread using (_lock.EnterScope()) { if (HasStarted() && !SetPriorityLive(value)) { throw new ThreadStateException(SR.ThreadState_SetPriorityFailed); } _priority = value; } if (value != ThreadPriority.Normal) { _mayNeedResetForThreadPool = true; } } } public ThreadState ThreadState => ((ThreadState)_threadState & PublicThreadStateMask); private bool GetThreadStateBit(ThreadState bit) { Debug.Assert((bit & ThreadState.Stopped) == 0, "ThreadState.Stopped bit may be stale; use GetThreadState instead."); return (_threadState & (int)bit) != 0; } private int SetThreadStateBit(ThreadState bit) { return Interlocked.Or(ref _threadState, (int)bit); } private int ClearThreadStateBit(ThreadState bit) { return Interlocked.And(ref _threadState, ~(int)bit); } internal void SetWaitSleepJoinState() { Debug.Assert(this == CurrentThread); Debug.Assert(!GetThreadStateBit(ThreadState.WaitSleepJoin)); SetThreadStateBit(ThreadState.WaitSleepJoin); } internal void ClearWaitSleepJoinState() { Debug.Assert(this == CurrentThread); Debug.Assert(GetThreadStateBit(ThreadState.WaitSleepJoin)); ClearThreadStateBit(ThreadState.WaitSleepJoin); } private static int VerifyTimeoutMilliseconds(int millisecondsTimeout) { if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( nameof(millisecondsTimeout), millisecondsTimeout, SR.ArgumentOutOfRange_NeedNonNegOrNegative1); } return millisecondsTimeout; } /// <summary> /// Max value to be passed into <see cref="SpinWait(int)"/> for optimal delaying. Currently, the value comes from /// defaults in CoreCLR's Thread::InitializeYieldProcessorNormalized(). This value is supposed to be normalized to be /// appropriate for the processor. /// TODO: See issue https://github.com/dotnet/corert/issues/4430 /// </summary> internal const int OptimalMaxSpinWaitsPerSpinIteration = 8; // Max iterations to be done in RhSpinWait. // RhSpinWait does not switch GC modes and we want to avoid native spinning in coop mode for too long. private const int SpinWaitCoopThreshold = 1024; internal static void SpinWaitInternal(int iterations) { Debug.Assert(iterations <= SpinWaitCoopThreshold); if (iterations > 0) { RuntimeImports.RhSpinWait(iterations); } } [MethodImpl(MethodImplOptions.NoInlining)] // Slow path method. Make sure that the caller frame does not pay for PInvoke overhead. private static void LongSpinWait(int iterations) { RuntimeImports.RhLongSpinWait(iterations); } public static void SpinWait(int iterations) { if (iterations > SpinWaitCoopThreshold) { LongSpinWait(iterations); } else { SpinWaitInternal(iterations); } } [MethodImpl(MethodImplOptions.NoInlining)] // Slow path method. Make sure that the caller frame does not pay for PInvoke overhead. public static bool Yield() => RuntimeImports.RhYield(); private void StartCore() { using (_lock.EnterScope()) { if (!GetThreadStateBit(ThreadState.Unstarted)) { throw new ThreadStateException(SR.ThreadState_AlreadyStarted); } bool waitingForThreadStart = false; GCHandle<Thread> threadHandle = new GCHandle<Thread>(this); try { if (!CreateThread(threadHandle)) { throw new OutOfMemoryException(); } // Skip cleanup if any asynchronous exception happens while waiting for the thread start waitingForThreadStart = true; // Wait until the new thread either dies or reports itself as started while (GetThreadStateBit(ThreadState.Unstarted) && !JoinInternal(0)) { Yield(); } waitingForThreadStart = false; } finally { Debug.Assert(!waitingForThreadStart, "Leaked threadHandle"); if (!waitingForThreadStart) { threadHandle.Dispose(); } } if (GetThreadStateBit(ThreadState.Unstarted)) { Exception? startException = _startException; _startException = null; throw new ThreadStartException(startException ?? new OutOfMemoryException()); } Debug.Assert(_startException == null); } } private static void StartThread(IntPtr parameter) { Thread thread = GCHandle<Thread>.FromIntPtr(parameter).Target; try { t_currentThread = thread; System.Threading.ManagedThreadId.SetForCurrentThread(thread._managedThreadId); thread.InitializeComOnNewThread(); } catch (Exception e) { thread._startException = e; #if TARGET_UNIX // This should go away once OnThreadExit stops using t_currentThread to signal // shutdown of the thread on Unix. thread._stopped!.Set(); #endif // Terminate the current thread. The creator thread will throw a ThreadStartException. return; } // Report success to the creator thread, which will free threadHandle int state = thread.ClearThreadStateBit(ThreadState.Unstarted); if ((state & (int)ThreadState.Background) == 0) { IncrementRunningForeground(); } try { StartHelper? startHelper = thread._startHelper; Debug.Assert(startHelper != null); thread._startHelper = null; startHelper.Run(); } catch (Exception ex) when (ExceptionHandling.IsHandledByGlobalHandler(ex)) { // the handler returned "true" means the exception is now "handled" and we should gracefully exit. } finally { thread.SetThreadStateBit(ThreadState.Stopped); } } private static void StopThread(Thread thread) { if ((thread._threadState & (int)(ThreadState.Stopped | ThreadState.Aborted)) == 0) { thread.SetThreadStateBit(ThreadState.Stopped); } int state = thread.ClearThreadStateBit(ThreadState.Background); if ((state & (int)ThreadState.Background) == 0) { DecrementRunningForeground(); } } internal static void IncrementRunningForeground() { Interlocked.Increment(ref s_foregroundRunningCount); } internal static void DecrementRunningForeground() { if (Interlocked.Decrement(ref s_foregroundRunningCount) == 0) { // Interlocked.Decrement issues full memory barrier // so most recent write to s_allDone should be visible here s_allDone?.Set(); } } internal static void WaitForForegroundThreads() { Thread.CurrentThread.IsBackground = true; // last read/write inside `IsBackground` issues full barrier no matter of logic flow // so we can just read `s_foregroundRunningCount` if (s_foregroundRunningCount == 0) { // current thread is the last foreground thread, so let the runtime finish return; } Volatile.Write(ref s_allDone, new ManualResetEvent(false)); // other foreground threads could have their job finished meanwhile // Volatile.Write above issues release barrier // but we need acquire barrier to observe most recent write to s_foregroundRunningCount if (Volatile.Read(ref s_foregroundRunningCount) == 0) { return; } s_allDone.WaitOne(); } } }