From: Aaron Robinson Date: Mon, 10 May 2021 20:31:39 +0000 (-0700) Subject: NSAutoreleasePool instance should be added to all threads. (#52023) X-Git-Tag: submit/tizen/20210909.063632~1485 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=60735c6d3f61ea93b903fd36e1656b8c2bbb4a96;p=platform%2Fupstream%2Fdotnet%2Fruntime.git NSAutoreleasePool instance should be added to all threads. (#52023) Add an NSAutoreleasePool to all managed create threads including the Main and Finalizer. This expands the current support where support was only added to ThreadPool threads. New feature switch was created and the ThreadPool one was removed. - System.Threading.Thread.EnableAutoreleasePool Updated AutoReleaseTest for the new scenarios. --- diff --git a/docs/workflow/trimming/feature-switches.md b/docs/workflow/trimming/feature-switches.md index 84f4aad6fab..bc939002281 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. | | BuiltInComInteropSupport | System.Runtime.InteropServices.BuiltInComInterop.IsSupported | Built-in COM support is trimmed when set to false. | diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index dcd11139bfa..67a5eab17c8 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 008332d3443..dfc2f617b3b 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 542de57517f..6f45022d10b 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 1a94c44f2a2..eb31d0f928d 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -1035,7 +1035,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; @@ -1061,7 +1061,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 1821750337f..86b99fcb832 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 cc71b577a74..22c6826d7be 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 b09b028c9ae..a303400cbd7 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 9efb8648a92..1ac8d476c25 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 ccf2af54101..deaafb66d1b 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 62f2b7eacd9..a7adbd9c026 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 @@ public: }; #endif // FEATURE_COMINTEROP -// When the thread starts running, make sure it is running in the correct apartment -// and context. -BOOL Thread::PrepareApartmentAndContext() +void Thread::FinishInitialization() { CONTRACTL { THROWS; @@ -4748,11 +4764,8 @@ BOOL Thread::PrepareApartmentAndContext() m_fInitializeSpyRegistered = true; } #endif // FEATURE_COMINTEROP - - return TRUE; } - #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // TS_InSTA (0x00004000) -> 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 f4c66fe0c53..50bf121eb04 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 @@ public: // 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 @@ public: 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 @@ public: #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + void FinishInitialization(); + #ifdef FEATURE_COMINTEROP bool IsDisableComObjectEagerCleanup() { @@ -1622,7 +1636,7 @@ private: 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 @@ public: public: - //-------------------------------------------------------------- // Constructor. //-------------------------------------------------------------- @@ -1764,16 +1777,15 @@ public: //-------------------------------------------------------------- // 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 @@ public: // 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 @@ public: 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 @@ public: // 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ private: // 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 @@ private: 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 2302e2e4619..68e4caf6e69 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 61dcb5b5a7b..3b747687397 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 55e6938440c..80dac9fdd76 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 @@ -1914,7 +1914,6 @@ - @@ -2035,4 +2034,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 00000000000..aea7d44ed1f --- /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 5baaa386b34..aa6e8c6f8d5 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 @@ namespace System.Threading Delegate start = _start; _start = null!; +#if FEATURE_OBJCMARSHAL + if (AutoreleasePool.EnableAutoreleasePool) + AutoreleasePool.CreateAutoreleasePool(); +#endif + if (start is ThreadStart threadStart) { threadStart(); @@ -76,6 +81,14 @@ namespace System.Threading 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 6991b82daf8..8e378ce9bf4 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 @@ using System.Runtime.Versioning; 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 bb55916ef78..f08e4ba4d7f 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 @@ namespace System.Threading // // 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 b6b4f889a81..66e5328b631 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 00000000000..2b19af074be --- /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 72d8dbbf923..cd190c8ea0a 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 1a9df287e8f..62975338ea8 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 110ebee75c5..e6564335b94 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 96d2ed23788..78502016cc2 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 0b1f39741d0..1e59f65a70e 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 e3869ca5b5f..394a526c552 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 7dabfc87a2b..c5f0f4306a7 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 1814a296fee..c506de46b2b 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 8bc03710399..4287f1cc2a1 100644 --- a/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj +++ b/src/tests/Interop/ObjectiveC/AutoReleaseTest/AutoReleaseTest.csproj @@ -4,6 +4,9 @@ true true + + +