diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/__Finalizer.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/__Finalizer.cs index dce232600ff1ce..e0573bf5c1b830 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/__Finalizer.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/__Finalizer.cs @@ -20,10 +20,6 @@ internal static class __Finalizer [UnmanagedCallersOnly(EntryPoint = "ProcessFinalizers")] public static void ProcessFinalizers() { -#if INPLACE_RUNTIME - System.Runtime.FinalizerInitRunner.DoInitialize(); -#endif - while (true) { // Wait until there's some work to be done. If true is returned we should finalize objects, diff --git a/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp b/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp index 7f49f0830c241d..730edbc87a3a3b 100644 --- a/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp @@ -29,14 +29,30 @@ CLREventStatic g_FinalizerDoneEvent; static HANDLE g_lowMemoryNotification = NULL; +#ifdef TARGET_WINDOWS +static bool g_ComAndFlsInitSucceeded = false; +#endif + EXTERN_C void QCALLTYPE ProcessFinalizers(); -// Unmanaged front-end to the finalizer thread. We require this because at the point the GC creates the -// finalizer thread we can't run managed code. Instead this method waits +// Unmanaged front-end to the finalizer thread. We require this because at the point when this thread is +// created we can't run managed code. Instead this method waits // for the first finalization request (by which time everything must be up and running) and kicks off the // managed portion of the thread at that point uint32_t WINAPI FinalizerStart(void* pContext) { +#ifdef TARGET_WINDOWS + g_ComAndFlsInitSucceeded = PalInitComAndFlsSlot(); + // handshake with EE initialization, as now we can attach Thread objects to native threads. + UInt32_BOOL res = PalSetEvent(g_FinalizerDoneEvent.GetOSEvent()); + ASSERT(res); + + // if FLS initialization failed do not attach the current thread and just exit instead. + // we are going to fail the runtime initialization. + if (!g_ComAndFlsInitSucceeded) + return 0; +#endif // DEBUG + HANDLE hFinalizerEvent = (HANDLE)pContext; PalSetCurrentThreadName(".NET Finalizer"); @@ -86,12 +102,16 @@ bool RhInitializeFinalization() return true; } -void RhEnableFinalization() +#ifdef TARGET_WINDOWS +bool RhWaitForFinalizerThreadStart() { - g_FinalizerEvent.Set(); + g_FinalizerDoneEvent.Wait(INFINITE,FALSE); + g_FinalizerDoneEvent.Reset(); + return g_ComAndFlsInitSucceeded; } +#endif -EXTERN_C void QCALLTYPE RhInitializeFinalizerThread() +void RhEnableFinalization() { g_FinalizerEvent.Set(); } diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp index c0801faed16620..91fd2db9260622 100644 --- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp @@ -43,11 +43,18 @@ GPTR_DECL(MethodTable, g_pFreeObjectEEType); GPTR_IMPL(Thread, g_pFinalizerThread); bool RhInitializeFinalization(); +#ifdef TARGET_WINDOWS +bool RhWaitForFinalizerThreadStart(); +#endif // Perform any runtime-startup initialization needed by the GC, HandleTable or environmental code in gcenv.ee. // Returns true on success or false if a subsystem failed to initialize. bool InitializeGC() { + // Give some headstart to the finalizer thread by launching it early. + if (!RhInitializeFinalization()) + return false; + // Initialize the special MethodTable used to mark free list entries in the GC heap. g_FreeObjectEEType.InitializeAsGcFreeType(); g_pFreeObjectEEType = &g_FreeObjectEEType; @@ -78,13 +85,17 @@ bool InitializeGC() if (FAILED(hr)) return false; - if (!RhInitializeFinalization()) - return false; - // Initialize HandleTable. if (!GCHandleUtilities::GetGCHandleManager()->Initialize()) return false; +#ifdef TARGET_WINDOWS + // By now finalizer thread should have initialized FLS slot for thread cleanup notifications. + // And ensured that COM is initialized (must happen before allocating FLS slot). + // Make sure that this was done. + if (!RhWaitForFinalizerThreadStart()) + return false; +#endif return true; } diff --git a/src/coreclr/nativeaot/Runtime/PalRedhawk.h b/src/coreclr/nativeaot/Runtime/PalRedhawk.h index 6b93fc11a323e7..adc2deaf208e5b 100644 --- a/src/coreclr/nativeaot/Runtime/PalRedhawk.h +++ b/src/coreclr/nativeaot/Runtime/PalRedhawk.h @@ -295,6 +295,7 @@ typedef uint32_t (__stdcall *BackgroundCallback)(_In_opt_ void* pCallbackContext REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalSetCurrentThreadName(const char* name); #ifdef TARGET_WINDOWS REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalSetCurrentThreadNameW(const WCHAR* name); +REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalInitComAndFlsSlot(); #endif REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalStartBackgroundGCThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext); REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalStartFinalizerThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext); @@ -321,7 +322,6 @@ REDHAWK_PALIMPORT uint32_t REDHAWK_PALAPI PalCompatibleWaitAny(UInt32_BOOL alert REDHAWK_PALIMPORT HANDLE PalCreateLowMemoryResourceNotification(); REDHAWK_PALIMPORT void REDHAWK_PALAPI PalAttachThread(void* thread); -REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalDetachThread(void* thread); REDHAWK_PALIMPORT uint64_t PalGetCurrentOSThreadId(); diff --git a/src/coreclr/nativeaot/Runtime/amd64/AsmMacros.inc b/src/coreclr/nativeaot/Runtime/amd64/AsmMacros.inc index 4a3437f2f0618b..5d5cde13bced18 100644 --- a/src/coreclr/nativeaot/Runtime/amd64/AsmMacros.inc +++ b/src/coreclr/nativeaot/Runtime/amd64/AsmMacros.inc @@ -329,7 +329,6 @@ TAILJMP_RAX TEXTEQU ;; ;; CONSTANTS -- INTEGER ;; -TSF_Attached equ 01h TSF_SuppressGcStress equ 08h TSF_DoNotTriggerGc equ 10h diff --git a/src/coreclr/nativeaot/Runtime/arm64/AsmMacros.h b/src/coreclr/nativeaot/Runtime/arm64/AsmMacros.h index 90e1b5d7779947..89393563f51473 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/AsmMacros.h +++ b/src/coreclr/nativeaot/Runtime/arm64/AsmMacros.h @@ -9,13 +9,11 @@ ;; ;; CONSTANTS -- INTEGER ;; -TSF_Attached equ 0x01 TSF_SuppressGcStress equ 0x08 TSF_DoNotTriggerGc equ 0x10 TSF_SuppressGcStress__OR__TSF_DoNotTriggerGC equ 0x18 ;; Bit position for the flags above, to be used with tbz/tbnz instructions -TSF_Attached_Bit equ 0 TSF_SuppressGcStress_Bit equ 3 TSF_DoNotTriggerGc_Bit equ 4 diff --git a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc index 8ee2e79f44fde0..289d9ef15ac28d 100644 --- a/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc +++ b/src/coreclr/nativeaot/Runtime/i386/AsmMacros.inc @@ -110,7 +110,6 @@ endm ;; ;; CONSTANTS -- INTEGER ;; -TSF_Attached equ 01h TSF_SuppressGcStress equ 08h TSF_DoNotTriggerGc equ 10h diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 77b00b10541ad7..a1a774a63ec691 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -1096,7 +1096,10 @@ bool Thread::IsDetached() void Thread::SetDetached() { ASSERT(!IsStateSet(TSF_Detached)); + ASSERT(IsStateSet(TSF_Attached)); + SetState(TSF_Detached); + ClearState(TSF_Attached); } bool Thread::IsActivationPending() diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index b4a2b0b5a62465..a431cd1434314b 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -189,7 +189,9 @@ class Thread : private RuntimeThreadLocals { TSF_Unknown = 0x00000000, // Threads are created in this state TSF_Attached = 0x00000001, // Thread was inited by first U->M transition on this thread - TSF_Detached = 0x00000002, // Thread was detached by DllMain + // ...Prior to setting this bit the state is TSF_Unknown. + TSF_Detached = 0x00000002, // Thread was detached and no longer can run managed code. + // ...TSF_Attached is cleared when TSF_Detached is set. TSF_SuppressGcStress = 0x00000008, // Do not allow gc stress on this thread, used in DllMain // ...and on the Finalizer thread TSF_DoNotTriggerGc = 0x00000010, // Do not allow hijacking of this thread, also intended to diff --git a/src/coreclr/nativeaot/Runtime/threadstore.cpp b/src/coreclr/nativeaot/Runtime/threadstore.cpp index 6e32929ddb6b55..dbd321ffcff4e6 100644 --- a/src/coreclr/nativeaot/Runtime/threadstore.cpp +++ b/src/coreclr/nativeaot/Runtime/threadstore.cpp @@ -110,9 +110,16 @@ void ThreadStore::AttachCurrentThread(bool fAcquireThreadStoreLock) // we want to avoid at construction time because the loader lock is held then. Thread * pAttachingThread = RawGetCurrentThread(); - // The thread was already initialized, so it is already attached + if (pAttachingThread->IsDetached()) + { + ASSERT_UNCONDITIONALLY("Attempt to execute managed code after the .NET runtime thread state has been destroyed."); + RhFailFast(); + } + + // The thread was already initialized, so it is already attached. if (pAttachingThread->IsInitialized()) { + ASSERT((pAttachingThread->m_ThreadStateFlags & Thread::TSF_Attached) != 0); return; } @@ -156,12 +163,8 @@ void ThreadStore::DetachCurrentThread() return; } - // Unregister from OS notifications - // This can return false if a thread did not register for OS notification. - if (!PalDetachThread(pDetachingThread)) - { - return; - } + // detach callback should not call us twice + ASSERT(!pDetachingThread->IsDetached()); // Run pre-mortem callbacks while we still can run managed code and not holding locks. // NOTE: background GC threads are attached/suspendable threads, but should not run ordinary diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 24172b9c3707e5..a928e7018da25f 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -517,17 +517,6 @@ extern "C" void PalAttachThread(void* thread) UnmaskActivationSignal(); } -// Detach thread from OS notifications. -// Parameters: -// thread - thread to detach -// Return: -// true if the thread was detached, false if there was no attached thread -extern "C" bool PalDetachThread(void* thread) -{ - UNREFERENCED_PARAMETER(thread); - return true; -} - #if !defined(USE_PORTABLE_HELPERS) && !defined(FEATURE_RX_THUNKS) REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDLE hTemplateModule, uint32_t templateRva, size_t templateSize, void** newThunksOut) diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp index 844a1080e2def8..0b8b0cb6400325 100644 --- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp @@ -53,13 +53,47 @@ static uint32_t g_flsIndex = FLS_OUT_OF_INDEXES; void __stdcall FiberDetachCallback(void* lpFlsData) { ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES); + ASSERT(g_flsIndex != NULL); ASSERT(lpFlsData == FlsGetValue(g_flsIndex)); - if (lpFlsData != NULL) + // The current fiber is the home fiber of a thread, so the thread is shutting down + RuntimeThreadShutdown(lpFlsData); +} + +REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInitComAndFlsSlot() +{ + ASSERT(g_flsIndex == FLS_OUT_OF_INDEXES); + + // Making finalizer thread MTA early ensures that COM is initialized before we initialize our thread + // termination callback. + HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hr)) + return false; + + // We use fiber detach callbacks to run our thread shutdown code because the fiber detach + // callback is made without the OS loader lock + g_flsIndex = FlsAlloc(FiberDetachCallback); + return g_flsIndex != FLS_OUT_OF_INDEXES; +} + +// Register the thread with OS to be notified when thread is about to be destroyed +// It fails fast if a different thread was already registered with the current fiber. +// Parameters: +// thread - thread to attach +REDHAWK_PALEXPORT void REDHAWK_PALAPI PalAttachThread(void* thread) +{ + void* threadFromCurrentFiber = FlsGetValue(g_flsIndex); + + if (threadFromCurrentFiber != NULL) { - // The current fiber is the home fiber of a thread, so the thread is shutting down - RuntimeThreadShutdown(lpFlsData); + ASSERT_UNCONDITIONALLY("Multiple threads encountered from a single fiber"); + RhFailFast(); } + + // Associate the current fiber with the current thread. This makes the current fiber the thread's "home" + // fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber + // is destroyed, we consider the thread to be destroyed. + FlsSetValue(g_flsIndex, thread); } static HMODULE LoadKernel32dll() @@ -159,14 +193,6 @@ void InitializeCurrentProcessCpuCount() // initialization and false on failure. REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() { - // We use fiber detach callbacks to run our thread shutdown code because the fiber detach - // callback is made without the OS loader lock - g_flsIndex = FlsAlloc(FiberDetachCallback); - if (g_flsIndex == FLS_OUT_OF_INDEXES) - { - return false; - } - GCConfig::Initialize(); if (!GCToOSInterface::Initialize()) @@ -179,54 +205,6 @@ REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() return true; } -// Register the thread with OS to be notified when thread is about to be destroyed -// It fails fast if a different thread was already registered with the current fiber. -// Parameters: -// thread - thread to attach -REDHAWK_PALEXPORT void REDHAWK_PALAPI PalAttachThread(void* thread) -{ - void* threadFromCurrentFiber = FlsGetValue(g_flsIndex); - - if (threadFromCurrentFiber != NULL) - { - ASSERT_UNCONDITIONALLY("Multiple threads encountered from a single fiber"); - RhFailFast(); - } - - // Associate the current fiber with the current thread. This makes the current fiber the thread's "home" - // fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber - // is destroyed, we consider the thread to be destroyed. - FlsSetValue(g_flsIndex, thread); -} - -// Detach thread from OS notifications. -// It fails fast if some other thread value was attached to the current fiber. -// Parameters: -// thread - thread to detach -// Return: -// true if the thread was detached, false if there was no attached thread -REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalDetachThread(void* thread) -{ - ASSERT(g_flsIndex != FLS_OUT_OF_INDEXES); - void* threadFromCurrentFiber = FlsGetValue(g_flsIndex); - - if (threadFromCurrentFiber == NULL) - { - // we've seen this thread, but not this fiber. It must be a "foreign" fiber that was - // borrowing this thread. - return false; - } - - if (threadFromCurrentFiber != thread) - { - ASSERT_UNCONDITIONALLY("Detaching a thread from the wrong fiber"); - RhFailFast(); - } - - FlsSetValue(g_flsIndex, NULL); - return true; -} - extern "C" uint64_t PalQueryPerformanceCounter() { return GCToOSInterface::QueryPerformanceCounter(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 7bb3b0f58e70e5..71646b278f0f3c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -201,7 +201,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs deleted file mode 100644 index d0021229b7522c..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; - -namespace System.Runtime -{ - internal static class FinalizerInitRunner - { - // Here, we are subscribing to a callback from the runtime. This callback is made from the finalizer - // thread before any objects are finalized. - [RuntimeExport("InitializeFinalizerThread")] - public static void DoInitialize() - { - // Make sure that the finalizer thread is CoInitialized before any objects are finalized. If this - // fails, it will throw an exception and that will go unhandled, triggering a FailFast. - Thread.InitializeComForFinalizerThread(); - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index b27d8de76e17b6..4fe7ee2e706106 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -94,9 +94,6 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) RhWaitForPendingFinalizers(allowReentrantWait ? 1 : 0); } - [LibraryImport(RuntimeLibrary)] - internal static partial void RhInitializeFinalizerThread(); - // Get maximum GC generation number. [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetMaxGcGeneration")] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs index 3cf12153d1f6d6..f9e49db5323045 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs @@ -137,10 +137,6 @@ private static bool SetApartmentStateUnchecked(ApartmentState state, bool throwO partial void InitializeComOnNewThread(); - internal static void InitializeComForFinalizerThread() - { - } - public void DisableComObjectEagerCleanup() { } public void Interrupt() => WaitSubsystem.Interrupt(this); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs index 15f8c25ae42eee..c579f4b3a00e32 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs @@ -24,8 +24,6 @@ public sealed partial class Thread private ApartmentState _initialApartmentState = ApartmentState.Unknown; - private static volatile bool s_comInitializedOnFinalizerThread; - partial void PlatformSpecificInitialize(); // Platform-specific initialization of foreign threads, i.e. threads not created by Thread.Start @@ -301,27 +299,11 @@ private void InitializeComOnNewThread() InitializeCom(_initialApartmentState); } - internal static void InitializeComForFinalizerThread() - { - InitializeCom(); - - // Prevent re-initialization of COM model on finalizer thread - t_comState |= ComState.Locked; - - s_comInitializedOnFinalizerThread = true; - } - private static void InitializeComForThreadPoolThread() { - // Initialized COM - take advantage of implicit MTA initialized by the finalizer thread - SpinWait sw = default(SpinWait); - while (!s_comInitializedOnFinalizerThread) - { - RuntimeImports.RhInitializeFinalizerThread(); - sw.SpinOnce(0); - } - - // Prevent re-initialization of COM model on threadpool threads + // Process-wide COM is initialized very early before any managed code can run. + // Assume it is done. + // Prevent re-initialization of COM model on threadpool threads from the default one. t_comState |= ComState.Locked; } diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs b/src/coreclr/nativeaot/Test.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs deleted file mode 100644 index 08ca09dd867bbc..00000000000000 --- a/src/coreclr/nativeaot/Test.CoreLib/src/System/Runtime/InitializeFinalizerThread.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; - -namespace System.Runtime -{ - internal static class FinalizerInitRunner - { - // Here, we are subscribing to a callback from the runtime. This callback is made from the finalizer - // thread before any objects are finalized. - public static void DoInitialize() - { - } - } -} diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj index b838c2ed7c14b7..7395abd155da45 100644 --- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj +++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj @@ -229,7 +229,6 @@ -