diff --git a/docs/workflow/trimming/feature-switches.md b/docs/workflow/trimming/feature-switches.md index 1171f8fb4b6d2..415b5f862146d 100644 --- a/docs/workflow/trimming/feature-switches.md +++ b/docs/workflow/trimming/feature-switches.md @@ -17,7 +17,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif | HttpActivityPropagationSupport | System.Net.Http.EnableActivityPropagation | Any dependency related to diagnostics support for System.Net.Http is trimmed when set to false | | UseNativeHttpHandler | System.Net.Http.UseNativeHttpHandler | HttpClient uses by default platform native implementation of HttpMessageHandler if set to true. | | StartupHookSupport | System.StartupHookProvider.IsSupported | Startup hooks are disabled when set to false. Startup hook related functionality can be trimmed. | -| TBD | System.Threading.ThreadPool.EnableDispatchAutoreleasePool | When set to true, creates an NSAutoreleasePool around each thread pool work item on applicable platforms. | +| TBD | System.Threading.Thread.EnableAutoreleasePool | When set to true, creates an NSAutoreleasePool for each thread and thread pool work item on applicable platforms. | | CustomResourceTypesSupport | System.Resources.ResourceManager.AllowCustomResourceTypes | Use of custom resource types is disabled when set to false. ResourceManager code paths that use reflection for custom types can be trimmed. | | EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization | System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization | BinaryFormatter serialization support is trimmed when set to false. | diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index dcd11139bfa9e..67a5eab17c868 100644 --- a/src/coreclr/clr.featuredefines.props +++ b/src/coreclr/clr.featuredefines.props @@ -35,6 +35,10 @@ true + + true + + $(DefineConstants);FEATURE_ARRAYSTUB_AS_IL $(DefineConstants);FEATURE_MULTICASTSTUB_AS_IL @@ -44,6 +48,7 @@ $(DefineConstants);FEATURE_COMWRAPPERS $(DefineConstants);FEATURE_COMINTEROP $(DefineConstants);FEATURE_COMINTEROP_APARTMENT_SUPPORT + $(DefineConstants);FEATURE_OBJCMARSHAL $(DefineConstants);FEATURE_MANAGED_ETW $(DefineConstants);FEATURE_MANAGED_ETW_CHANNELS $(DefineConstants);FEATURE_PERFTRACING diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index 008332d3443f3..dfc2f617b3b37 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -93,6 +93,10 @@ if(CLR_CMAKE_TARGET_WIN32) add_definitions(-DFEATURE_COMINTEROP_UNMANAGED_ACTIVATION) endif(CLR_CMAKE_TARGET_WIN32) +if(CLR_CMAKE_TARGET_OSX OR CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS) + add_definitions(-DFEATURE_OBJCMARSHAL) +endif(CLR_CMAKE_TARGET_OSX OR CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS) + add_definitions(-DFEATURE_BASICFREEZE) add_definitions(-DFEATURE_CORECLR) add_definitions(-DFEATURE_CORESYSTEM) diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 542de57517f57..6f45022d10b77 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -1656,9 +1656,17 @@ INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThre AppDomain * pDomain = pThread->GetDomain(); pDomain->SetRootAssembly(pMeth->GetAssembly()); + // Perform additional managed thread initialization. + // This would is normally done in the runtime when a managed + // thread is started, but is done here instead since the + // Main thread wasn't started by the runtime. + Thread::InitializationForManagedThreadInNative(pThread); + RunStartupHooks(); hr = RunMain(pMeth, 1, &iRetVal, stringArgs); + + Thread::CleanUpForManagedThreadInNative(pThread); } } diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 77120d5f81d07..e8c83ecf31f1a 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -1030,7 +1030,7 @@ void EEStartupHelper() g_MiniMetaDataBuffMaxSize, MEM_COMMIT, PAGE_READWRITE); #endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS -#endif // CROSSGEN_COMPILE +#endif // !CROSSGEN_COMPILE g_fEEStarted = TRUE; g_EEStartupStatus = S_OK; @@ -1056,7 +1056,6 @@ void EEStartupHelper() // Perform CoreLib consistency check if requested g_CoreLib.CheckExtended(); - #endif // _DEBUG #endif // !CROSSGEN_COMPILE diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 1821750337f4b..86b99fcb83201 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -881,6 +881,11 @@ DEFINE_FIELD_U(_priority, ThreadBaseObject, m_Priority) DEFINE_CLASS(THREAD, Threading, Thread) DEFINE_METHOD(THREAD, INTERNAL_GET_CURRENT_THREAD, InternalGetCurrentThread, SM_RetIntPtr) DEFINE_METHOD(THREAD, START_CALLBACK, StartCallback, IM_RetVoid) +#ifdef FEATURE_OBJCMARSHAL +DEFINE_CLASS(AUTORELEASEPOOL, Threading, AutoreleasePool) +DEFINE_METHOD(AUTORELEASEPOOL, CREATEAUTORELEASEPOOL, CreateAutoreleasePool, SM_RetVoid) +DEFINE_METHOD(AUTORELEASEPOOL, DRAINAUTORELEASEPOOL, DrainAutoreleasePool, SM_RetVoid) +#endif // FEATURE_OBJCMARSHAL DEFINE_CLASS(IOCB_HELPER, Threading, _IOCompletionCallback) DEFINE_METHOD(IOCB_HELPER, PERFORM_IOCOMPLETION_CALLBACK, PerformIOCompletionCallback, SM_UInt_UInt_PtrNativeOverlapped_RetVoid) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index cc71b577a74c7..22c6826d7be8e 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -2745,12 +2745,8 @@ ep_rt_thread_setup (void) { STATIC_CONTRACT_NOTHROW; - EX_TRY - { - SetupThread (); - } - EX_CATCH {} - EX_END_CATCH(SwallowAllExceptions); + Thread* thread_handle = SetupThreadNoThrow (); + EP_ASSERT (thread_handle != NULL); } static diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index b09b028c9ae1a..a303400cbd744 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -222,6 +222,7 @@ void FinalizerThread::WaitForFinalizerEvent (CLREvent *event) } static BOOL s_FinalizerThreadOK = FALSE; +static BOOL s_InitializedFinalizerThreadForPlatform = FALSE; VOID FinalizerThread::FinalizerThreadWorker(void *args) { @@ -289,6 +290,15 @@ VOID FinalizerThread::FinalizerThreadWorker(void *args) bPriorityBoosted = TRUE; } + // The Finalizer thread is started very early in EE startup. We deferred + // some initialization until a point we are sure the EE is up and running. At + // this point we make a single attempt and if it fails won't try again. + if (!s_InitializedFinalizerThreadForPlatform) + { + s_InitializedFinalizerThreadForPlatform = TRUE; + Thread::InitializationForManagedThreadInNative(GetFinalizerThread()); + } + JitHost::Reclaim(); GetFinalizerThread()->DisablePreemptiveGC(); @@ -330,6 +340,9 @@ VOID FinalizerThread::FinalizerThreadWorker(void *args) // acceptable. SignalFinalizationDone(TRUE); } + + if (s_InitializedFinalizerThreadForPlatform) + Thread::CleanUpForManagedThreadInNative(GetFinalizerThread()); } DWORD WINAPI FinalizerThread::FinalizerThreadStart(void *args) diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 9efb8648a92b9..1ac8d476c2509 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -1361,7 +1361,7 @@ namespace EX_TRY { - args.Thread = SetupUnstartedThread(FALSE); + args.Thread = SetupUnstartedThread(SUTF_ThreadStoreLockAlreadyTaken); } EX_CATCH { @@ -1382,7 +1382,7 @@ namespace ClrFlsSetThreadType(ThreadType_GC); args->Thread->SetGCSpecial(true); STRESS_LOG_RESERVE_MEM(GC_STRESSLOG_MULTIPLY); - args->HasStarted = !!args->Thread->HasStarted(false); + args->HasStarted = !!args->Thread->HasStarted(); Thread* thread = args->Thread; auto threadStart = args->ThreadStart; @@ -1407,7 +1407,7 @@ namespace return false; } - args.Thread->SetBackground(TRUE, FALSE); + args.Thread->SetBackground(TRUE); args.Thread->StartThread(); // Wait for the thread to be in its main loop diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index ccf2af5410122..deaafb66d1b63 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -9073,20 +9073,17 @@ HRESULT ProfToEEInterfaceImpl::SetupThreadForReJIT() { LIMITED_METHOD_CONTRACT; - HRESULT hr = S_OK; - EX_TRY + Thread* pThread = GetThreadNULLOk(); + if (pThread == NULL) { - if (GetThreadNULLOk() == NULL) - { - SetupThread(); - } - - Thread *pThread = GetThreadNULLOk(); - pThread->SetProfilerCallbackStateFlags(COR_PRF_CALLBACKSTATE_REJIT_WAS_CALLED); + HRESULT hr = S_OK; + pThread = SetupThreadNoThrow(&hr); + if (pThread == NULL) + return hr; } - EX_CATCH_HRESULT(hr); - return hr; + pThread->SetProfilerCallbackStateFlags(COR_PRF_CALLBACKSTATE_REJIT_WAS_CALLED); + return S_OK; } HRESULT ProfToEEInterfaceImpl::RequestReJIT(ULONG cFunctions, // in diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 62f2b7eacd919..a7adbd9c0266e 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -570,73 +570,22 @@ DWORD Thread::StartThread() } CONTRACTL_END; - DWORD dwRetVal = (DWORD) -1; #ifdef _DEBUG - _ASSERTE (m_Creater.IsCurrentThread()); - m_Creater.Clear(); + _ASSERTE (m_Creator.IsCurrentThread()); + m_Creator.Clear(); #endif _ASSERTE (GetThreadHandle() != INVALID_HANDLE_VALUE); - dwRetVal = ::ResumeThread(GetThreadHandle()); - - + DWORD dwRetVal = ::ResumeThread(GetThreadHandle()); return dwRetVal; } - // Class static data: LONG Thread::m_DebugWillSyncCount = -1; LONG Thread::m_DetachCount = 0; LONG Thread::m_ActiveDetachCount = 0; -//------------------------------------------------------------------------- -// Public function: SetupThreadNoThrow() -// Creates Thread for current thread if not previously created. -// Returns NULL for failure (usually due to out-of-memory.) -//------------------------------------------------------------------------- -Thread* SetupThreadNoThrow(HRESULT *pHR) -{ - CONTRACTL { - NOTHROW; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - HRESULT hr = S_OK; - - Thread *pThread = GetThreadNULLOk(); - if (pThread != NULL) - { - return pThread; - } - - EX_TRY - { - pThread = SetupThread(); - } - EX_CATCH - { - // We failed SetupThread. GET_EXCEPTION() may depend on Thread object. - if (__pException == NULL) - { - hr = E_OUTOFMEMORY; - } - else - { - hr = GET_EXCEPTION()->GetHR(); - } - } - EX_END_CATCH(SwallowAllExceptions); - - if (pHR) - { - *pHR = hr; - } - - return pThread; -} - -void DeleteThread(Thread* pThread) +static void DeleteThread(Thread* pThread) { CONTRACTL { NOTHROW; @@ -670,7 +619,7 @@ void DeleteThread(Thread* pThread) } } -void EnsurePreemptive() +static void EnsurePreemptive() { WRAPPER_NO_CONTRACT; Thread *pThread = GetThreadNULLOk(); @@ -720,7 +669,7 @@ Thread* SetupThread() // that call into managed code. In that case, a call to SetupThread here must // find the correct Thread object and install it into TLS. - if (ThreadStore::s_pThreadStore->m_PendingThreadCount != 0) + if (ThreadStore::s_pThreadStore->GetPendingThreadCount() != 0) { DWORD ourOSThreadId = ::GetCurrentThreadId(); { @@ -774,10 +723,7 @@ Thread* SetupThread() Holder,DeleteThread> threadHolder(pThread); SetupTLSForThread(); - - if (!pThread->InitThread() || - !pThread->PrepareApartmentAndContext()) - ThrowOutOfMemory(); + pThread->InitThread(); // reset any unstarted bits on the thread object FastInterlockAnd((ULONG *) &pThread->m_State, ~Thread::TS_Unstarted); @@ -852,6 +798,9 @@ Thread* SetupThread() FastInterlockOr((ULONG *) &pThread->m_State, Thread::TS_TPWorkerThread); } + // Initialize the thread for the platform as the final step. + pThread->FinishInitialization(); + #ifdef FEATURE_EVENT_TRACE ETW::ThreadLog::FireThreadCreated(pThread); #endif // FEATURE_EVENT_TRACE @@ -859,6 +808,53 @@ Thread* SetupThread() return pThread; } +//------------------------------------------------------------------------- +// Public function: SetupThreadNoThrow() +// Creates Thread for current thread if not previously created. +// Returns NULL for failure (usually due to out-of-memory.) +//------------------------------------------------------------------------- +Thread* SetupThreadNoThrow(HRESULT *pHR) +{ + CONTRACTL { + NOTHROW; + if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + Thread *pThread = GetThreadNULLOk(); + if (pThread != NULL) + { + return pThread; + } + + EX_TRY + { + pThread = SetupThread(); + } + EX_CATCH + { + // We failed SetupThread. GET_EXCEPTION() may depend on Thread object. + if (__pException == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + hr = GET_EXCEPTION()->GetHR(); + } + } + EX_END_CATCH(SwallowAllExceptions); + + if (pHR) + { + *pHR = hr; + } + + return pThread; +} + //------------------------------------------------------------------------- // Public function: SetupUnstartedThread() // This sets up a Thread object for an exposed System.Thread that @@ -868,7 +864,7 @@ Thread* SetupThread() // // When there is, complete the setup with code:Thread::HasStarted() //------------------------------------------------------------------------- -Thread* SetupUnstartedThread(BOOL bRequiresTSL) +Thread* SetupUnstartedThread(SetupUnstartedThreadFlags flags) { CONTRACTL { THROWS; @@ -878,10 +874,16 @@ Thread* SetupUnstartedThread(BOOL bRequiresTSL) Thread* pThread = new Thread(); + if (flags & SUTF_ThreadStoreLockAlreadyTaken) + { + _ASSERTE(ThreadStore::HoldingThreadStore()); + pThread->SetThreadStateNC(Thread::TSNC_TSLTakenForStartup); + } + FastInterlockOr((ULONG *) &pThread->m_State, (Thread::TS_Unstarted | Thread::TS_WeOwn)); - ThreadStore::AddThread(pThread, bRequiresTSL); + ThreadStore::AddThread(pThread); return pThread; } @@ -1374,7 +1376,7 @@ Thread::Thread() #ifdef _DEBUG dbg_m_cSuspendedThreads = 0; dbg_m_cSuspendedThreadsWithoutOSLock = 0; - m_Creater.Clear(); + m_Creator.Clear(); m_dwUnbreakableLockCount = 0; #endif @@ -1469,7 +1471,6 @@ Thread::Thread() #endif // TRACK_SYNC m_PreventAsync = 0; - m_pDomain = NULL; #ifdef FEATURE_COMINTEROP m_fDisableComObjectEagerCleanup = false; #endif //FEATURE_COMINTEROP @@ -1562,7 +1563,7 @@ Thread::Thread() m_ioThreadPoolCompletionCount = 0; m_monitorLockContentionCount = 0; - InitContext(); + m_pDomain = SystemDomain::System()->DefaultDomain(); // Do not expose thread until it is fully constructed g_pThinLockThreadIdDispenser->NewId(this, this->m_ThreadId); @@ -1612,7 +1613,7 @@ Thread::Thread() //-------------------------------------------------------------------- // Failable initialization occurs here. //-------------------------------------------------------------------- -BOOL Thread::InitThread() +void Thread::InitThread() { CONTRACTL { THROWS; @@ -1739,9 +1740,6 @@ BOOL Thread::InitThread() { ThrowOutOfMemory(); } - - _ASSERTE(ret); // every failure case for ret should throw. - return ret; } // Allocate all the handles. When we are kicking of a new thread, we can call @@ -1775,12 +1773,11 @@ BOOL Thread::AllocHandles() return fOK; } - //-------------------------------------------------------------------- // This is the alternate path to SetupThread/InitThread. If we created // an unstarted thread, we have SetupUnstartedThread/HasStarted. //-------------------------------------------------------------------- -BOOL Thread::HasStarted(BOOL bRequiresTSL) +BOOL Thread::HasStarted() { CONTRACTL { NOTHROW; @@ -1803,11 +1800,9 @@ BOOL Thread::HasStarted(BOOL bRequiresTSL) if (GetThreadNULLOk() == this) return TRUE; - _ASSERTE(GetThreadNULLOk() == 0); _ASSERTE(HasValidThreadHandle()); - BOOL fKeepTLS = FALSE; BOOL fCanCleanupCOMState = FALSE; BOOL res = TRUE; @@ -1822,25 +1817,17 @@ BOOL Thread::HasStarted(BOOL bRequiresTSL) // which will be thrown in Thread.Start as an internal exception EX_TRY { - // - // Initialization must happen in the following order - hosts like SQL Server depend on this. - // - SetupTLSForThread(); - fCanCleanupCOMState = TRUE; - res = PrepareApartmentAndContext(); - if (!res) - { - ThrowOutOfMemory(); - } - InitThread(); SetThread(this); SetAppDomain(m_pDomain); - ThreadStore::TransferStartedThread(this, bRequiresTSL); + fCanCleanupCOMState = TRUE; + FinishInitialization(); + + ThreadStore::TransferStartedThread(this); #ifdef FEATURE_EVENT_TRACE ETW::ThreadLog::FireThreadCreated(this); @@ -1857,90 +1844,88 @@ BOOL Thread::HasStarted(BOOL bRequiresTSL) } EX_END_CATCH(SwallowAllExceptions); -FAILURE: if (res == FALSE) - { - if (m_fPreemptiveGCDisabled) - { - m_fPreemptiveGCDisabled = FALSE; - } - _ASSERTE (HasThreadState(TS_Unstarted)); - - SetThreadState(TS_FailStarted); + goto FAILURE; - if (GetThreadNULLOk() != NULL && IsAbortRequested()) - UnmarkThreadForAbort(); + FastInterlockOr((ULONG *) &m_State, TS_FullyInitialized); - if (!fKeepTLS) - { -#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT - // - // Undo our call to PrepareApartmentAndContext above, so we don't leak a CoInitialize - // If we're keeping TLS, then the host's call to ExitTask will clean this up instead. - // - if (fCanCleanupCOMState) - { - // The thread pointer in TLS may not be set yet, if we had a failure before we set it. - // So we'll set it up here (we'll unset it a few lines down). - SetThread(this); - CleanupCOMState(); - } -#endif - FastInterlockDecrement(&ThreadStore::s_pThreadStore->m_PendingThreadCount); - // One of the components of OtherThreadsComplete() has changed, so check whether - // we should now exit the EE. - ThreadStore::CheckForEEShutdown(); - DecExternalCount(/*holdingLock*/ !bRequiresTSL); - SetThread(NULL); - SetAppDomain(NULL); - } +#ifdef DEBUGGING_SUPPORTED + // + // If we're debugging, let the debugger know that this + // thread is up and running now. + // + if (CORDebuggerAttached()) + { + g_pDebugInterface->ThreadCreated(this); } else { - FastInterlockOr((ULONG *) &m_State, TS_FullyInitialized); - -#ifdef DEBUGGING_SUPPORTED - // - // If we're debugging, let the debugger know that this - // thread is up and running now. - // - if (CORDebuggerAttached()) - { - g_pDebugInterface->ThreadCreated(this); - } - else - { - LOG((LF_CORDB, LL_INFO10000, "ThreadCreated() not called due to CORDebuggerAttached() being FALSE for thread 0x%x\n", GetThreadId())); - } + LOG((LF_CORDB, LL_INFO10000, "ThreadCreated() not called due to CORDebuggerAttached() being FALSE for thread 0x%x\n", GetThreadId())); + } #endif // DEBUGGING_SUPPORTED #ifdef PROFILING_SUPPORTED - // If a profiler is running, let them know about the new thread. - // - // The call to IsGCSpecial is crucial to avoid a deadlock. See code:Thread::m_fGCSpecial for more - // information - if (!IsGCSpecial()) - { - BEGIN_PIN_PROFILER(CORProfilerTrackThreads()); - BOOL gcOnTransition = GC_ON_TRANSITIONS(FALSE); // disable GCStress 2 to avoid the profiler receiving a RuntimeThreadSuspended notification even before the ThreadCreated notification + // If a profiler is running, let them know about the new thread. + // + // The call to IsGCSpecial is crucial to avoid a deadlock. See code:Thread::m_fGCSpecial for more + // information + if (!IsGCSpecial()) + { + BEGIN_PIN_PROFILER(CORProfilerTrackThreads()); + BOOL gcOnTransition = GC_ON_TRANSITIONS(FALSE); // disable GCStress 2 to avoid the profiler receiving a RuntimeThreadSuspended notification even before the ThreadCreated notification - { - GCX_PREEMP(); - g_profControlBlock.pProfInterface->ThreadCreated((ThreadID) this); - } + { + GCX_PREEMP(); + g_profControlBlock.pProfInterface->ThreadCreated((ThreadID) this); + } - GC_ON_TRANSITIONS(gcOnTransition); + GC_ON_TRANSITIONS(gcOnTransition); - DWORD osThreadId = ::GetCurrentThreadId(); - g_profControlBlock.pProfInterface->ThreadAssignedToOSThread( - (ThreadID) this, osThreadId); - END_PIN_PROFILER(); - } + DWORD osThreadId = ::GetCurrentThreadId(); + g_profControlBlock.pProfInterface->ThreadAssignedToOSThread( + (ThreadID) this, osThreadId); + END_PIN_PROFILER(); + } #endif // PROFILING_SUPPORTED + + // Reset the ThreadStoreLock state flag since the thread + // has now been started. + ResetThreadStateNC(Thread::TSNC_TSLTakenForStartup); + return TRUE; + +FAILURE: + if (m_fPreemptiveGCDisabled) + { + m_fPreemptiveGCDisabled = FALSE; } + _ASSERTE (HasThreadState(TS_Unstarted)); - return res; + SetThreadState(TS_FailStarted); + + if (GetThreadNULLOk() != NULL && IsAbortRequested()) + UnmarkThreadForAbort(); + +#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT + // + // Undo the platform context initialization, so we don't leak a CoInitialize. + // + if (fCanCleanupCOMState) + { + // The thread pointer in TLS may not be set yet, if we had a failure before we set it. + // So we'll set it up here (we'll unset it a few lines down). + SetThread(this); + CleanupCOMState(); + } +#endif + FastInterlockDecrement(&ThreadStore::s_pThreadStore->m_PendingThreadCount); + // One of the components of OtherThreadsComplete() has changed, so check whether + // we should now exit the EE. + ThreadStore::CheckForEEShutdown(); + DecExternalCount(/*holdingLock*/ HasThreadStateNC(Thread::TSNC_TSLTakenForStartup)); + SetThread(NULL); + SetAppDomain(NULL); + return FALSE; } BOOL Thread::AllocateIOCompletionContext() @@ -2086,6 +2071,48 @@ BOOL Thread::CreateNewThread(SIZE_T stackSize, LPTHREAD_START_ROUTINE start, voi return bRet; } +void Thread::InitializationForManagedThreadInNative(_In_ Thread* pThread) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_TRIGGERS; + PRECONDITION(pThread != NULL); + } + CONTRACTL_END; + +#ifdef FEATURE_OBJCMARSHAL + { + GCX_COOP_THREAD_EXISTS(pThread); + PREPARE_NONVIRTUAL_CALLSITE(METHOD__AUTORELEASEPOOL__CREATEAUTORELEASEPOOL); + DECLARE_ARGHOLDER_ARRAY(args, 0); + CALL_MANAGED_METHOD_NORET(args); + } +#endif // FEATURE_OBJCMARSHAL +} + +void Thread::CleanUpForManagedThreadInNative(_In_ Thread* pThread) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + GC_TRIGGERS; + PRECONDITION(pThread != NULL); + } + CONTRACTL_END; + +#ifdef FEATURE_OBJCMARSHAL + { + GCX_COOP_THREAD_EXISTS(pThread); + PREPARE_NONVIRTUAL_CALLSITE(METHOD__AUTORELEASEPOOL__DRAINAUTORELEASEPOOL); + DECLARE_ARGHOLDER_ARRAY(args, 0); + CALL_MANAGED_METHOD_NORET(args); + } +#endif // FEATURE_OBJCMARSHAL +} + HANDLE Thread::CreateUtilityThread(Thread::StackSizeBucket stackSizeBucket, LPTHREAD_START_ROUTINE start, void *args, LPCWSTR pName, DWORD flags, DWORD* pThreadId) { LIMITED_METHOD_CONTRACT; @@ -2127,7 +2154,6 @@ HANDLE Thread::CreateUtilityThread(Thread::StackSizeBucket stackSizeBucket, LPTH return hThread; } - // Represent the value of DEFAULT_STACK_SIZE as passed in the property bag to the host during construction static unsigned long s_defaultStackSizeProperty = 0; @@ -2303,7 +2329,7 @@ BOOL Thread::CreateNewOSThread(SIZE_T sizeToCommitOrReserve, LPTHREAD_START_ROUT FastInterlockIncrement(&ThreadStore::s_pThreadStore->m_PendingThreadCount); #ifdef _DEBUG - m_Creater.SetToCurrentThread(); + m_Creator.SetToCurrentThread(); #endif return TRUE; @@ -2405,10 +2431,8 @@ int Thread::DecExternalCount(BOOL holdingLock) ToggleGC = pCurThread->PreemptiveGCDisabled(); if (ToggleGC) - { pCurThread->EnablePreemptiveGC(); } - } GCX_ASSERT_PREEMP(); @@ -4564,7 +4588,7 @@ void Thread::SafeUpdateLastThrownObject(void) // Background threads must be counted, because the EE should shut down when the // last non-background thread terminates. But we only count running ones. -void Thread::SetBackground(BOOL isBack, BOOL bRequiresTSL) +void Thread::SetBackground(BOOL isBack) { CONTRACTL { NOTHROW; @@ -4576,12 +4600,11 @@ void Thread::SetBackground(BOOL isBack, BOOL bRequiresTSL) if (isBack == !!IsBackground()) return; + BOOL lockHeld = HasThreadStateNC(Thread::TSNC_TSLTakenForStartup); + _ASSERTE(!lockHeld || (lockHeld && ThreadStore::HoldingThreadStore())); + LOG((LF_SYNC, INFO3, "SetBackground obtain lock\n")); - ThreadStoreLockHolder TSLockHolder(FALSE); - if (bRequiresTSL) - { - TSLockHolder.Acquire(); - } + ThreadStoreLockHolder TSLockHolder(!lockHeld); if (IsDead()) { @@ -4625,11 +4648,6 @@ void Thread::SetBackground(BOOL isBack, BOOL bRequiresTSL) ThreadStore::s_pThreadStore->m_ThreadCount); } } - - if (bRequiresTSL) - { - TSLockHolder.Release(); - } } #ifdef FEATURE_COMINTEROP @@ -4691,9 +4709,7 @@ class ApartmentSpyImpl : public IUnknownCommon AS_InSTA (0) @@ -4831,7 +4844,6 @@ Thread::ApartmentState Thread::GetApartmentRare(Thread::ApartmentState as) return as; } - // Retrieve the explicit apartment state of the current thread. There are three possible // states: thread hosts an STA, thread is part of the MTA or thread state is // undecided. The last state may indicate that the apartment has not been set at @@ -4860,7 +4872,6 @@ Thread::ApartmentState Thread::GetExplicitApartment() return as; } - Thread::ApartmentState Thread::GetFinalApartment() { CONTRACTL @@ -5135,7 +5146,6 @@ ThreadStore::ThreadStore() m_DeadThreadCount(0), m_DeadThreadCountForGCTrigger(0), m_TriggerGCForDeadThreads(false), - m_GuidCreated(FALSE), m_HoldingThread(0) { CONTRACTL { @@ -5230,7 +5240,7 @@ void ThreadStore::UnlockThreadStore() } // AddThread adds 'newThread' to m_ThreadList -void ThreadStore::AddThread(Thread *newThread, BOOL bRequiresTSL) +void ThreadStore::AddThread(Thread *newThread) { CONTRACTL { NOTHROW; @@ -5240,11 +5250,10 @@ void ThreadStore::AddThread(Thread *newThread, BOOL bRequiresTSL) LOG((LF_SYNC, INFO3, "AddThread obtain lock\n")); - ThreadStoreLockHolder TSLockHolder(FALSE); - if (bRequiresTSL) - { - TSLockHolder.Acquire(); - } + BOOL lockHeld = newThread->HasThreadStateNC(Thread::TSNC_TSLTakenForStartup); + _ASSERTE(!lockHeld || (lockHeld && ThreadStore::HoldingThreadStore())); + + ThreadStoreLockHolder TSLockHolder(!lockHeld); s_pThreadStore->m_ThreadList.InsertTail(newThread); @@ -5259,11 +5268,6 @@ void ThreadStore::AddThread(Thread *newThread, BOOL bRequiresTSL) _ASSERTE(!newThread->IsBackground()); _ASSERTE(!newThread->IsDead()); - - if (bRequiresTSL) - { - TSLockHolder.Release(); - } } // this function is just desgined to avoid deadlocks during abnormal process termination, and should not be used for any other purpose @@ -5368,22 +5372,31 @@ BOOL ThreadStore::RemoveThread(Thread *target) // When a thread is created as unstarted. Later it may get started, in which case // someone calls Thread::HasStarted() on that physical thread. This completes // the Setup and calls here. -void ThreadStore::TransferStartedThread(Thread *thread, BOOL bRequiresTSL) +void ThreadStore::TransferStartedThread(Thread *thread) { CONTRACTL { - THROWS; + NOTHROW; GC_TRIGGERS; + PRECONDITION(thread != NULL); } CONTRACTL_END; _ASSERTE(GetThreadNULLOk() == thread); - LOG((LF_SYNC, INFO3, "TransferUnstartedThread obtain lock\n")); - ThreadStoreLockHolder TSLockHolder(FALSE); - if (bRequiresTSL) - { - TSLockHolder.Acquire(); - } + BOOL lockHeld = thread->HasThreadStateNC(Thread::TSNC_TSLTakenForStartup); + + // This ASSERT is correct for one of the following reasons. + // - The lock is not currently held which means it will be taken below. + // - The thread was created in an Unstarted state and the lock is + // being held by the creator thread. The only thing we know for sure + // is that the lock is held and not by this thread. + _ASSERTE(!lockHeld + || (lockHeld + && s_pThreadStore->m_HoldingThread != NULL + && !ThreadStore::HoldingThreadStore())); + + LOG((LF_SYNC, INFO3, "TransferStartedThread obtain lock\n")); + ThreadStoreLockHolder TSLockHolder(!lockHeld); _ASSERTE(s_pThreadStore->DbgFindThread(thread)); _ASSERTE(thread->HasValidThreadHandle()); @@ -5391,14 +5404,8 @@ void ThreadStore::TransferStartedThread(Thread *thread, BOOL bRequiresTSL) _ASSERTE(thread->IsUnstarted()); _ASSERTE(!thread->IsDead()); - if (thread->m_State & Thread::TS_AbortRequested) - { - PAL_CPP_THROW(EEException *, new EEException(COR_E_THREADABORTED)); - } - // Of course, m_ThreadCount is already correct since it includes started and // unstarted threads. - s_pThreadStore->m_UnstartedThreadCount--; // We only count background threads that have been started @@ -5413,12 +5420,6 @@ void ThreadStore::TransferStartedThread(Thread *thread, BOOL bRequiresTSL) FastInterlockAnd((ULONG *) &thread->m_State, ~Thread::TS_Unstarted); FastInterlockOr((ULONG *) &thread->m_State, Thread::TS_LegalToJoin); - // release ThreadStore Crst to avoid Crst Violation when calling HandleThreadAbort later - if (bRequiresTSL) - { - TSLockHolder.Release(); - } - // One of the components of OtherThreadsComplete() has changed, so check whether // we should now exit the EE. CheckForEEShutdown(); @@ -5752,36 +5753,6 @@ void ThreadStore::WaitForOtherThreads() } } - -// Every EE process can lazily create a GUID that uniquely identifies it (for -// purposes of remoting). -const GUID &ThreadStore::GetUniqueEEId() -{ - CONTRACTL { - NOTHROW; - GC_TRIGGERS; - } - CONTRACTL_END; - - if (!m_GuidCreated) - { - ThreadStoreLockHolder TSLockHolder(TRUE); - if (!m_GuidCreated) - { - HRESULT hr = ::CoCreateGuid(&m_EEGuid); - - _ASSERTE(SUCCEEDED(hr)); - if (SUCCEEDED(hr)) - m_GuidCreated = TRUE; - } - - if (!m_GuidCreated) - return IID_NULL; - } - return m_EEGuid; -} - - #ifdef _DEBUG BOOL ThreadStore::DbgFindThread(Thread *target) { @@ -7150,20 +7121,6 @@ T_CONTEXT *Thread::GetFilterContext(void) #ifndef DACCESS_COMPILE -void Thread::InitContext() -{ - CONTRACTL { - THROWS; - if (GetThreadNULLOk()) {GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} - } - CONTRACTL_END; - - // this should only be called when initializing a thread - _ASSERTE(m_pDomain == NULL); - GCX_COOP_NO_THREAD_BROKEN(); - m_pDomain = SystemDomain::System()->DefaultDomain(); -} - void Thread::ClearContext() { CONTRACTL { @@ -7187,7 +7144,7 @@ BOOL Thread::HaveExtraWorkForFinalizer() { LIMITED_METHOD_CONTRACT; - return m_ThreadTasks + return RequireSyncBlockCleanup() || ThreadpoolMgr::HaveTimerInfosToFlush() || Thread::CleanupNeededForFinalizedThread() || (m_DetachCount > 0) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index f4c66fe0c53f1..50bf121eb0418 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -544,8 +544,9 @@ enum ThreadpoolThreadType //*************************************************************************** // Public functions // -// Thread* GetThread() - returns current Thread -// Thread* SetupThread() - creates new Thread. +// Thread* GetThread() - returns current Thread. +// Thread* SetupThread() - creates a new Thread. +// Thread* SetupThreadNoThrow() - creates a new Thread without throwing. // Thread* SetupUnstartedThread() - creates new unstarted Thread which // (obviously) isn't in a TLS. // void DestroyThread() - the underlying logical thread is going @@ -580,8 +581,18 @@ enum ThreadpoolThreadType //--------------------------------------------------------------------------- Thread* SetupThread(); Thread* SetupThreadNoThrow(HRESULT *phresult = NULL); -// WARNING : only GC calls this with bRequiresTSL set to FALSE. -Thread* SetupUnstartedThread(BOOL bRequiresTSL=TRUE); + +enum SetupUnstartedThreadFlags +{ + SUTF_None = 0, + + // The ThreadStoreLock is being held during Thread startup. + SUTF_ThreadStoreLockAlreadyTaken = 1, + + // The default flags for the majority of threads. + SUTF_Default = SUTF_None, +}; +Thread* SetupUnstartedThread(SetupUnstartedThreadFlags flags = SUTF_Default); void DestroyThread(Thread *th); DWORD GetRuntimeId(); @@ -1124,7 +1135,7 @@ class Thread // unused = 0x00400000, - // unused = 0x00800000, + // unused = 0x00800000, TS_TPWorkerThread = 0x01000000, // is this a threadpool worker thread? TS_Interruptible = 0x02000000, // sitting in a Sleep(), Wait(), Join() @@ -1213,7 +1224,8 @@ class Thread TSNC_WinRTInitialized = 0x08000000, // the thread has initialized WinRT #endif // FEATURE_COMINTEROP - // TSNC_Unused = 0x10000000, + TSNC_TSLTakenForStartup = 0x10000000, // The ThreadStoreLock (TSL) is held by another mechansim during + // thread startup so can be skipped. TSNC_CallingManagedCodeDisabled = 0x20000000, // Use by multicore JIT feature to asert on calling managed code/loading module in background thread // Exception, system module is allowed, security demand is allowed @@ -1370,6 +1382,8 @@ class Thread #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + void FinishInitialization(); + #ifdef FEATURE_COMINTEROP bool IsDisableComObjectEagerCleanup() { @@ -1622,7 +1636,7 @@ class Thread DWORD dbg_m_cSuspendedThreads; // Count of suspended threads that we know are not in native code (and therefore cannot hold OS lock which prevents us calling out to host) DWORD dbg_m_cSuspendedThreadsWithoutOSLock; - EEThreadId m_Creater; + EEThreadId m_Creator; #endif // A thread may forbid its own suspension. For example when holding certain locks. @@ -1753,7 +1767,6 @@ class Thread public: - //-------------------------------------------------------------- // Constructor. //-------------------------------------------------------------- @@ -1764,16 +1777,15 @@ class Thread //-------------------------------------------------------------- // Failable initialization occurs here. //-------------------------------------------------------------- - BOOL InitThread(); + void InitThread(); BOOL AllocHandles(); //-------------------------------------------------------------- // If the thread was setup through SetupUnstartedThread, rather // than SetupThread, complete the setup here when the thread is // actually running. - // WARNING : only GC calls this with bRequiresTSL set to FALSE. //-------------------------------------------------------------- - BOOL HasStarted(BOOL bRequiresTSL=TRUE); + BOOL HasStarted(); // We don't want ::CreateThread() calls scattered throughout the source. // Create all new threads here. The thread is created as suspended, so @@ -1781,6 +1793,12 @@ class Thread // thread, or throw. BOOL CreateNewThread(SIZE_T stackSize, LPTHREAD_START_ROUTINE start, void *args, LPCWSTR pName=NULL); + // Functions used to perform initialization and cleanup on a managed thread + // that would normally occur if the thread was stated when the runtime was + // fully initialized and ready to run. + // Examples where this applies are the Main and Finalizer threads. + static void InitializationForManagedThreadInNative(_In_ Thread* pThread); + static void CleanUpForManagedThreadInNative(_In_ Thread* pThread); enum StackSizeBucket { @@ -2225,15 +2243,10 @@ class Thread return PTR_ThreadExceptionState(PTR_HOST_MEMBER_TADDR(Thread, this, m_ExceptionState)); } -public: - +private: // ClearContext are to be called only during shutdown void ClearContext(); -private: - // don't ever call these except when creating thread!!!!! - void InitContext(); - public: PTR_AppDomain GetDomain(INDEBUG(BOOL fMidContextTransitionOK = FALSE)) { @@ -2845,12 +2858,7 @@ class Thread // Indicate whether this thread should run in the background. Background threads // don't interfere with the EE shutting down. Whereas a running non-background // thread prevents us from shutting down (except through System.Exit(), of course) - // WARNING : only GC calls this with bRequiresTSL set to FALSE. - void SetBackground(BOOL isBack, BOOL bRequiresTSL=TRUE); - - // When the thread starts running, make sure it is running in the correct apartment - // and context. - BOOL PrepareApartmentAndContext(); + void SetBackground(BOOL isBack); #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // Retrieve the apartment state of the current thread. There are three possible @@ -4669,7 +4677,6 @@ class ThreadStore { friend class Thread; friend class ThreadSuspend; - friend Thread* SetupThread(); friend class AppDomain; #ifdef DACCESS_COMPILE friend class ClrDataAccess; @@ -4685,8 +4692,7 @@ class ThreadStore static void UnlockThreadStore(); // Add a Thread to the ThreadStore - // WARNING : only GC calls this with bRequiresTSL set to FALSE. - static void AddThread(Thread *newThread, BOOL bRequiresTSL=TRUE); + static void AddThread(Thread *newThread); // RemoveThread finds the thread in the ThreadStore and discards it. static BOOL RemoveThread(Thread *target); @@ -4694,8 +4700,7 @@ class ThreadStore static BOOL CanAcquireLock(); // Transfer a thread from the unstarted to the started list. - // WARNING : only GC calls this with bRequiresTSL set to FALSE. - static void TransferStartedThread(Thread *target, BOOL bRequiresTSL=TRUE); + static void TransferStartedThread(Thread *target); // Before using the thread list, be sure to take the critical section. Otherwise // it can change underneath you, perhaps leading to an exception after Remove. @@ -4703,10 +4708,6 @@ class ThreadStore static Thread *GetAllThreadList(Thread *Prev, ULONG mask, ULONG bits); static Thread *GetThreadList(Thread *Prev); - // Every EE process can lazily create a GUID that uniquely identifies it (for - // purposes of remoting). - const GUID &GetUniqueEEId(); - // We shut down the EE when the last non-background thread terminates. This event // is used to signal the main thread when this condition occurs. void WaitForOtherThreads(); @@ -4760,7 +4761,7 @@ class ThreadStore // m_PendingThreadCount is used to solve a race condition. The main thread could // start another thread running and then exit. The main thread might then start // tearing down the EE before the new thread moves itself out of m_UnstartedThread- - // Count in TransferUnstartedThread. This count is atomically bumped in + // Count in TransferStartedThread. This count is atomically bumped in // CreateNewThread, and atomically reduced within a locked thread store. // // m_DeadThreadCount is the subset of m_ThreadCount which have died. The Win32 @@ -4790,16 +4791,19 @@ class ThreadStore LONG m_UnstartedThreadCount; LONG m_BackgroundThreadCount; LONG m_PendingThreadCount; +public: + LONG GetPendingThreadCount () + { + LIMITED_METHOD_CONTRACT; + return m_PendingThreadCount; + } +private: LONG m_DeadThreadCount; LONG m_DeadThreadCountForGCTrigger; bool m_TriggerGCForDeadThreads; private: - // Space for the lazily-created GUID. - GUID m_EEGuid; - BOOL m_GuidCreated; - // Even in the release product, we need to know what thread holds the lock on // the ThreadStore. This is so we never deadlock when the GC thread halts a // thread that holds this lock. diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 2302e2e4619dc..68e4caf6e69e9 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -438,7 +438,7 @@ DWORD Thread::ResumeThread() DWORD res = ::ResumeThread(m_ThreadHandleForResume); _ASSERTE (res != 0 && "Thread is not previously suspended"); #ifdef _DEBUG_IMPL - _ASSERTE (!m_Creater.IsCurrentThread()); + _ASSERTE (!m_Creator.IsCurrentThread()); if ((res != (DWORD)-1) && (res != 0)) { Thread * pCurThread = GetThreadNULLOk(); diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.OSX.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.OSX.xml index 61dcb5b5a7bf2..3b747687397c0 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.OSX.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Substitutions.OSX.xml @@ -1,7 +1,7 @@ - - + + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index b8f3489e46526..b91ee01b9144f 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1912,7 +1912,6 @@ - @@ -2033,4 +2032,8 @@ Interop\Windows\Kernel32\Interop.Threading.cs + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/AutoreleasePool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/AutoreleasePool.cs new file mode 100644 index 0000000000000..aea7d44ed1fb1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/AutoreleasePool.cs @@ -0,0 +1,49 @@ +// 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.Runtime.InteropServices; + +namespace System.Threading +{ + internal static class AutoreleasePool + { + private static bool CheckEnableAutoreleasePool() + { + const string feature = "System.Threading.Thread.EnableAutoreleasePool"; +#if !CORECLR + return AppContextConfigHelper.GetBooleanConfig(feature, false); +#else + bool isEnabled = CLRConfig.GetBoolValue(feature, out bool isSet); + if (!isSet) + return false; + + return isEnabled; +#endif + } + + public static bool EnableAutoreleasePool { get; } = CheckEnableAutoreleasePool(); + + [ThreadStatic] + private static IntPtr s_AutoreleasePoolInstance; + + internal static void CreateAutoreleasePool() + { + if (EnableAutoreleasePool) + { + Debug.Assert(s_AutoreleasePoolInstance == IntPtr.Zero); + s_AutoreleasePoolInstance = Interop.Sys.CreateAutoreleasePool(); + } + } + + internal static void DrainAutoreleasePool() + { + if (EnableAutoreleasePool + && s_AutoreleasePoolInstance != IntPtr.Zero) + { + Interop.Sys.DrainAutoreleasePool(s_AutoreleasePoolInstance); + s_AutoreleasePoolInstance = IntPtr.Zero; + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index 5baaa386b3474..aa6e8c6f8d582 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -63,6 +63,11 @@ private void RunWorker() Delegate start = _start; _start = null!; +#if FEATURE_OBJCMARSHAL + if (AutoreleasePool.EnableAutoreleasePool) + AutoreleasePool.CreateAutoreleasePool(); +#endif + if (start is ThreadStart threadStart) { threadStart(); @@ -76,6 +81,14 @@ private void RunWorker() parameterizedThreadStart(startArg); } + +#if FEATURE_OBJCMARSHAL + // There is no need to wrap this "clean up" code in a finally block since + // if an exception is thrown above, the process is going to terminate. + // Optimize for the most common case - no exceptions escape a thread. + if (AutoreleasePool.EnableAutoreleasePool) + AutoreleasePool.DrainAutoreleasePool(); +#endif } private void InitializeCulture() diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.AutoreleasePool.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.AutoreleasePool.OSX.cs index 6991b82daf84a..8e378ce9bf434 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.AutoreleasePool.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.AutoreleasePool.OSX.cs @@ -6,12 +6,6 @@ namespace System.Threading { - public static partial class ThreadPool - { - internal static bool EnableDispatchAutoreleasePool { get; } = - AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableDispatchAutoreleasePool", false); - } - internal sealed partial class ThreadPoolWorkQueue { [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs index bb55916ef78f1..f08e4ba4d7fe0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs @@ -691,8 +691,8 @@ internal static bool Dispatch() // // Execute the workitem outside of any finally blocks, so that it can be aborted if needed. // -#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - if (ThreadPool.EnableDispatchAutoreleasePool) +#if FEATURE_OBJCMARSHAL + if (AutoreleasePool.EnableAutoreleasePool) { DispatchItemWithAutoreleasePool(workItem, currentThread); } diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index b6b4f889a81fb..66e5328b63156 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -110,12 +110,14 @@ true true true + true $(DefineConstants);FEATURE_MANAGED_ETW $(DefineConstants);FEATURE_MANAGED_ETW_CHANNELS $(DefineConstants);FEATURE_PERFTRACING + $(DefineConstants);FEATURE_OBJCMARSHAL @@ -131,6 +133,8 @@ + diff --git a/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.OSX.xml b/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.OSX.xml new file mode 100644 index 0000000000000..2b19af074bedc --- /dev/null +++ b/src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.OSX.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/mono/mono/metadata/class-internals.h b/src/mono/mono/metadata/class-internals.h index 72d8dbbf92374..cd190c8ea0ab7 100644 --- a/src/mono/mono/metadata/class-internals.h +++ b/src/mono/mono/metadata/class-internals.h @@ -962,6 +962,7 @@ typedef struct { MonoClass *threadabortexception_class; MonoClass *thread_class; MonoClass *internal_thread_class; + MonoClass *autoreleasepool_class; MonoClass *mono_method_message_class; MonoClass *field_info_class; MonoClass *method_info_class; diff --git a/src/mono/mono/metadata/domain.c b/src/mono/mono/metadata/domain.c index 1a9df287e8fb5..62975338ea8d9 100644 --- a/src/mono/mono/metadata/domain.c +++ b/src/mono/mono/metadata/domain.c @@ -365,6 +365,13 @@ mono_init_internal (const char *filename, const char *exe_filename, const char * /* There is only one thread class */ mono_defaults.internal_thread_class = mono_defaults.thread_class; +#if defined(HOST_DARWIN) + mono_defaults.autoreleasepool_class = mono_class_load_from_name ( + mono_defaults.corlib, "System.Threading", "AutoreleasePool"); +#else + mono_defaults.autoreleasepool_class = NULL; +#endif + mono_defaults.field_info_class = mono_class_load_from_name ( mono_defaults.corlib, "System.Reflection", "FieldInfo"); diff --git a/src/mono/mono/metadata/gc.c b/src/mono/mono/metadata/gc.c index 110ebee75c532..e6564335b949a 100644 --- a/src/mono/mono/metadata/gc.c +++ b/src/mono/mono/metadata/gc.c @@ -856,6 +856,7 @@ static gsize WINAPI finalizer_thread (gpointer unused) { gboolean wait = TRUE; + gboolean did_init_from_native = FALSE; mono_thread_set_name_constant_ignore_error (mono_thread_internal_current (), "Finalizer", MonoSetThreadNameFlag_None); @@ -878,6 +879,14 @@ finalizer_thread (gpointer unused) mono_thread_info_set_flags (MONO_THREAD_INFO_FLAGS_NONE); + /* The Finalizer thread doesn't initialize during creation because base managed + libraries may not be loaded yet. However, the first time the Finalizer is + to run managed finalizer, we can take this opportunity to initialize. */ + if (!did_init_from_native) { + did_init_from_native = TRUE; + mono_thread_init_from_native (); + } + mono_runtime_do_background_work (); /* Avoid posting the pending done event until there are pending finalizers */ @@ -896,6 +905,11 @@ finalizer_thread (gpointer unused) } } + /* If the initialization from native was done, do the clean up */ + if (did_init_from_native) { + mono_thread_cleanup_from_native (); + } + mono_finalizer_lock (); finalizer_thread_exited = TRUE; mono_coop_cond_signal (&exited_cond); diff --git a/src/mono/mono/metadata/object-internals.h b/src/mono/mono/metadata/object-internals.h index 96d2ed237884d..78502016cc2fa 100644 --- a/src/mono/mono/metadata/object-internals.h +++ b/src/mono/mono/metadata/object-internals.h @@ -533,8 +533,9 @@ typedef struct { TYPED_HANDLE_DECL (MonoStackFrame); typedef enum { - MONO_THREAD_FLAG_DONT_MANAGE = 1, // Don't wait for or abort this thread - MONO_THREAD_FLAG_NAME_SET = 2, // Thread name set from managed code + MONO_THREAD_FLAG_DONT_MANAGE = 1, // Don't wait for or abort this thread + MONO_THREAD_FLAG_NAME_SET = 2, // Thread name set from managed code + MONO_THREAD_FLAG_CLEANUP_FROM_NATIVE = 4, // Thread initialized in native so clean up in native } MonoThreadFlags; struct _MonoThreadInfo; diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index 0b1f39741d0d7..1e59f65a70e0f 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -4369,7 +4369,7 @@ prepare_thread_to_exec_main (MonoMethod *method) thread->apartment_state = ThreadApartmentState_MTA; } mono_thread_init_apartment_state (); - + mono_thread_init_from_native (); } static int diff --git a/src/mono/mono/metadata/threads-types.h b/src/mono/mono/metadata/threads-types.h index e3869ca5b5fdd..394a526c5524d 100644 --- a/src/mono/mono/metadata/threads-types.h +++ b/src/mono/mono/metadata/threads-types.h @@ -73,11 +73,11 @@ void mono_thread_callbacks_init (void); typedef enum { - MONO_THREAD_CREATE_FLAGS_NONE = 0x0, - MONO_THREAD_CREATE_FLAGS_THREADPOOL = 0x1, - MONO_THREAD_CREATE_FLAGS_DEBUGGER = 0x2, - MONO_THREAD_CREATE_FLAGS_FORCE_CREATE = 0x4, - MONO_THREAD_CREATE_FLAGS_SMALL_STACK = 0x8, + MONO_THREAD_CREATE_FLAGS_NONE = 0x00, + MONO_THREAD_CREATE_FLAGS_THREADPOOL = 0x01, + MONO_THREAD_CREATE_FLAGS_DEBUGGER = 0x02, + MONO_THREAD_CREATE_FLAGS_FORCE_CREATE = 0x04, + MONO_THREAD_CREATE_FLAGS_SMALL_STACK = 0x08, } MonoThreadCreateFlags; MonoInternalThread* @@ -216,6 +216,12 @@ void mono_thread_clear_and_set_state (MonoInternalThread *thread, MonoThreadStat void mono_thread_init_apartment_state (void); void mono_thread_cleanup_apartment_state (void); +/* There are some threads that need initialization that would normally + occur in managed code. Some threads occur prior to the runtime being + fully initialized so that must be done in native. For example, Main and Finalizer. */ +void mono_thread_init_from_native (void); +void mono_thread_cleanup_from_native (void); + void mono_threads_set_shutting_down (void); MONO_API MonoException* mono_thread_get_undeniable_exception (void); diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 7dabfc87a2b8d..c5f0f4306a7a5 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -3914,18 +3914,62 @@ mono_thread_init_apartment_state (void) #endif } -void +void mono_thread_cleanup_apartment_state (void) { #ifdef HOST_WIN32 MonoInternalThread* thread = mono_thread_internal_current (); - if (thread && thread->apartment_state != ThreadApartmentState_Unknown) { CoUninitialize (); } #endif } +void +mono_thread_init_from_native (void) +{ +#if defined(HOST_DARWIN) + MonoInternalThread* thread = mono_thread_internal_current (); + + g_assert (mono_defaults.autoreleasepool_class != NULL); + ERROR_DECL (error); + MONO_STATIC_POINTER_INIT (MonoMethod, create_autoreleasepool) + + create_autoreleasepool = mono_class_get_method_from_name_checked (mono_defaults.autoreleasepool_class, "CreateAutoreleasePool", 0, 0, error); + mono_error_assert_ok (error); + + MONO_STATIC_POINTER_INIT_END (MonoMethod, create_autoreleasepool) + + mono_runtime_invoke_handle_void (create_autoreleasepool, NULL_HANDLE, NULL, error); + mono_error_cleanup (error); + + thread->flags |= MONO_THREAD_FLAG_CLEANUP_FROM_NATIVE; +#endif +} + +void +mono_thread_cleanup_from_native (void) +{ +#if defined(HOST_DARWIN) + MonoInternalThread* thread = mono_thread_internal_current (); + if (!(thread->flags & MONO_THREAD_FLAG_CLEANUP_FROM_NATIVE)) + return; + + g_assert (mono_defaults.autoreleasepool_class != NULL); + ERROR_DECL (error); + MONO_STATIC_POINTER_INIT (MonoMethod, drain_autoreleasepool) + + drain_autoreleasepool = mono_class_get_method_from_name_checked (mono_defaults.autoreleasepool_class, "DrainAutoreleasePool", 0, 0, error); + mono_error_assert_ok (error); + + MONO_STATIC_POINTER_INIT_END (MonoMethod, drain_autoreleasepool) + + mono_runtime_invoke_handle_void (drain_autoreleasepool, NULL_HANDLE, NULL, error); + mono_error_cleanup (error); + +#endif +} + static void mono_thread_notify_change_state (MonoThreadState old_state, MonoThreadState new_state) { diff --git a/src/tests/Common/CLRTest.Execute.Bash.targets b/src/tests/Common/CLRTest.Execute.Bash.targets index 1814a296feee8..c506de46b2bdc 100644 --- a/src/tests/Common/CLRTest.Execute.Bash.targets +++ b/src/tests/Common/CLRTest.Execute.Bash.targets @@ -19,7 +19,7 @@ WARNING: When setting properties based on their current state (for example: - + + DependsOnTargets="FetchExternalPropertiesForXplat;$(BashScriptSnippetGen);GetIlasmRoundTripBashScript"> 0 + @(RuntimeHostConfigurationOption -> '-p "%(Identity)=%(Value)"', ' ') - <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"$CORE_ROOT/corerun" + <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"$CORE_ROOT/corerun" $(CoreRunArgs) + @(RuntimeHostConfigurationOption -> '-p "%(Identity)=%(Value)"', ' ') - <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"%CORE_ROOT%\corerun.exe" + <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"%CORE_ROOT%\corerun.exe" $(CoreRunArgs) + { + ObjectiveC.autoreleaseObject(obj); + evt.Set(); + }); + thread.Start(); + + evt.WaitOne(); + thread.Join(); + } + } + + private static void ValidateThreadPoolAutoRelease() + { + Console.WriteLine($"Running {nameof(ValidateThreadPoolAutoRelease)}..."); using (AutoResetEvent evt = new AutoResetEvent(false)) { int numReleaseCalls = ObjectiveC.getNumReleaseCalls(); diff --git a/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj b/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj index 8bc0371039965..4287f1cc2a154 100644 --- a/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj +++ b/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj @@ -4,6 +4,9 @@ true true + + +