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.
| 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. |
<FeatureInstantiatingStubAsIL Condition="'$(Platform)' != 'x86'">true</FeatureInstantiatingStubAsIL>
</PropertyGroup>
+ <PropertyGroup Condition="'$(TargetsOSX)' == 'true' OR '$(TargetsMacCatalyst)' == 'true' OR '$(TargetsiOS)' == 'true' OR '$(TargetstvOS)' == 'true'">
+ <FeatureObjCMarshal>true</FeatureObjCMarshal>
+ </PropertyGroup>
+
<PropertyGroup>
<DefineConstants Condition="'$(FeatureArrayStubAsIL)' == 'true'">$(DefineConstants);FEATURE_ARRAYSTUB_AS_IL</DefineConstants>
<DefineConstants Condition="'$(FeatureMulticastStubAsIL)' == 'true'">$(DefineConstants);FEATURE_MULTICASTSTUB_AS_IL</DefineConstants>
<DefineConstants Condition="'$(FeatureComWrappers)' == 'true'">$(DefineConstants);FEATURE_COMWRAPPERS</DefineConstants>
<DefineConstants Condition="'$(FeatureCominterop)' == 'true'">$(DefineConstants);FEATURE_COMINTEROP</DefineConstants>
<DefineConstants Condition="'$(FeatureCominteropApartmentSupport)' == 'true'">$(DefineConstants);FEATURE_COMINTEROP_APARTMENT_SUPPORT</DefineConstants>
+ <DefineConstants Condition="'$(FeatureObjCMarshal)' == 'true'">$(DefineConstants);FEATURE_OBJCMARSHAL</DefineConstants>
<DefineConstants Condition="'$(FeatureManagedEtw)' == 'true'">$(DefineConstants);FEATURE_MANAGED_ETW</DefineConstants>
<DefineConstants Condition="'$(FeatureManagedEtwChannels)' == 'true'">$(DefineConstants);FEATURE_MANAGED_ETW_CHANNELS</DefineConstants>
<DefineConstants Condition="'$(FeaturePerfTracing)' == 'true'">$(DefineConstants);FEATURE_PERFTRACING</DefineConstants>
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)
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);
}
}
g_MiniMetaDataBuffMaxSize, MEM_COMMIT, PAGE_READWRITE);
#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
-#endif // CROSSGEN_COMPILE
+#endif // !CROSSGEN_COMPILE
g_fEEStarted = TRUE;
g_EEStartupStatus = S_OK;
// Perform CoreLib consistency check if requested
g_CoreLib.CheckExtended();
-
#endif // _DEBUG
#endif // !CROSSGEN_COMPILE
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)
{
STATIC_CONTRACT_NOTHROW;
- EX_TRY
- {
- SetupThread ();
- }
- EX_CATCH {}
- EX_END_CATCH(SwallowAllExceptions);
+ Thread* thread_handle = SetupThreadNoThrow ();
+ EP_ASSERT (thread_handle != NULL);
}
static
}
static BOOL s_FinalizerThreadOK = FALSE;
+static BOOL s_InitializedFinalizerThreadForPlatform = FALSE;
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();
// acceptable.
SignalFinalizationDone(TRUE);
}
+
+ if (s_InitializedFinalizerThreadForPlatform)
+ Thread::CleanUpForManagedThreadInNative(GetFinalizerThread());
}
DWORD WINAPI FinalizerThread::FinalizerThreadStart(void *args)
EX_TRY
{
- args.Thread = SetupUnstartedThread(FALSE);
+ args.Thread = SetupUnstartedThread(SUTF_ThreadStoreLockAlreadyTaken);
}
EX_CATCH
{
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;
return false;
}
- args.Thread->SetBackground(TRUE, FALSE);
+ args.Thread->SetBackground(TRUE);
args.Thread->StartThread();
// Wait for the thread to be in its main loop
{
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
}
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;
}
}
-void EnsurePreemptive()
+static void EnsurePreemptive()
{
WRAPPER_NO_CONTRACT;
Thread *pThread = GetThreadNULLOk();
// 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();
{
Holder<Thread*,DoNothing<Thread*>,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);
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
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
//
// When there is, complete the setup with code:Thread::HasStarted()
//-------------------------------------------------------------------------
-Thread* SetupUnstartedThread(BOOL bRequiresTSL)
+Thread* SetupUnstartedThread(SetupUnstartedThreadFlags flags)
{
CONTRACTL {
THROWS;
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;
}
#ifdef _DEBUG
dbg_m_cSuspendedThreads = 0;
dbg_m_cSuspendedThreadsWithoutOSLock = 0;
- m_Creater.Clear();
+ m_Creator.Clear();
m_dwUnbreakableLockCount = 0;
#endif
#endif // TRACK_SYNC
m_PreventAsync = 0;
- m_pDomain = NULL;
#ifdef FEATURE_COMINTEROP
m_fDisableComObjectEagerCleanup = false;
#endif //FEATURE_COMINTEROP
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);
//--------------------------------------------------------------------
// Failable initialization occurs here.
//--------------------------------------------------------------------
-BOOL Thread::InitThread()
+void Thread::InitThread()
{
CONTRACTL {
THROWS;
{
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
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;
if (GetThreadNULLOk() == this)
return TRUE;
-
_ASSERTE(GetThreadNULLOk() == 0);
_ASSERTE(HasValidThreadHandle());
- BOOL fKeepTLS = FALSE;
BOOL fCanCleanupCOMState = FALSE;
BOOL res = TRUE;
// 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);
}
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()
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;
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;
FastInterlockIncrement(&ThreadStore::s_pThreadStore->m_PendingThreadCount);
#ifdef _DEBUG
- m_Creater.SetToCurrentThread();
+ m_Creator.SetToCurrentThread();
#endif
return TRUE;
ToggleGC = pCurThread->PreemptiveGCDisabled();
if (ToggleGC)
- {
pCurThread->EnablePreemptiveGC();
}
- }
GCX_ASSERT_PREEMP();
// 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;
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())
{
ThreadStore::s_pThreadStore->m_ThreadCount);
}
}
-
- if (bRequiresTSL)
- {
- TSLockHolder.Release();
- }
}
#ifdef FEATURE_COMINTEROP
};
#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;
m_fInitializeSpyRegistered = true;
}
#endif // FEATURE_COMINTEROP
-
- return TRUE;
}
-
#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT
// TS_InSTA (0x00004000) -> AS_InSTA (0)
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
return as;
}
-
Thread::ApartmentState Thread::GetFinalApartment()
{
CONTRACTL
m_DeadThreadCount(0),
m_DeadThreadCountForGCTrigger(0),
m_TriggerGCForDeadThreads(false),
- m_GuidCreated(FALSE),
m_HoldingThread(0)
{
CONTRACTL {
}
// AddThread adds 'newThread' to m_ThreadList
-void ThreadStore::AddThread(Thread *newThread, BOOL bRequiresTSL)
+void ThreadStore::AddThread(Thread *newThread)
{
CONTRACTL {
NOTHROW;
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);
_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
// 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());
_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
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();
}
}
-
-// 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)
{
#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 {
{
LIMITED_METHOD_CONTRACT;
- return m_ThreadTasks
+ return RequireSyncBlockCleanup()
|| ThreadpoolMgr::HaveTimerInfosToFlush()
|| Thread::CleanupNeededForFinalizedThread()
|| (m_DetachCount > 0)
//***************************************************************************
// 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
//---------------------------------------------------------------------------
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();
// unused = 0x00400000,
- // unused = 0x00800000,
+ // unused = 0x00800000,
TS_TPWorkerThread = 0x01000000, // is this a threadpool worker thread?
TS_Interruptible = 0x02000000, // sitting in a Sleep(), Wait(), Join()
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
#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT
+ void FinishInitialization();
+
#ifdef FEATURE_COMINTEROP
bool IsDisableComObjectEagerCleanup()
{
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.
public:
-
//--------------------------------------------------------------
// Constructor.
//--------------------------------------------------------------
//--------------------------------------------------------------
// 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
// 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
{
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))
{
// 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
{
friend class Thread;
friend class ThreadSuspend;
- friend Thread* SetupThread();
friend class AppDomain;
#ifdef DACCESS_COMPILE
friend class ClrDataAccess;
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);
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.
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();
// 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
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.
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();
<linker>
<assembly fullname="System.Private.CoreLib">
- <type fullname="System.Threading.ThreadPool" feature="System.Threading.ThreadPool.EnableDispatchAutoreleasePool" featurevalue="false">
- <method signature="System.Boolean get_EnableDispatchAutoreleasePool()" body="stub" value="false" />
+ <type fullname="System.Threading.AutoreleasePool" feature="System.Threading.Thread.EnableAutoreleasePool" featurevalue="false">
+ <method signature="System.Boolean get_EnableAutoreleasePool()" body="stub" value="false" />
</type>
</assembly>
</linker>
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.Unix.cs" Condition="'$(IsOSXLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.SunOS.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolWorkQueue.AutoreleasePool.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.FullGlobalizationData.Unix.cs" Condition="'$(UseMinimalGlobalizationData)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\DriveInfoInternal.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PersistedFiles.Unix.cs" />
<Link>Interop\Windows\Kernel32\Interop.Threading.cs</Link>
</Compile>
</ItemGroup>
+ <ItemGroup Condition="'$(IsOSXLike)' == 'true'">
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\AutoreleasePool.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolWorkQueue.AutoreleasePool.OSX.cs" />
+ </ItemGroup>
</Project>
--- /dev/null
+// 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;
+ }
+ }
+ }
+}
Delegate start = _start;
_start = null!;
+#if FEATURE_OBJCMARSHAL
+ if (AutoreleasePool.EnableAutoreleasePool)
+ AutoreleasePool.CreateAutoreleasePool();
+#endif
+
if (start is ThreadStart threadStart)
{
threadStart();
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()
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)]
//
// 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);
}
<FeaturePortableTimer Condition="'$(TargetsBrowser)' != 'true'">true</FeaturePortableTimer>
<FeaturePortableThreadPool Condition="'$(TargetsBrowser)' != 'true'">true</FeaturePortableThreadPool>
<FeaturePerfTracing Condition="'$(TargetsBrowser)' != 'true'">true</FeaturePerfTracing>
+ <FeatureObjCMarshal Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</FeatureObjCMarshal>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition="'$(FeatureManagedEtw)' == 'true'">$(DefineConstants);FEATURE_MANAGED_ETW</DefineConstants>
<DefineConstants Condition="'$(FeatureManagedEtwChannels)' == 'true'">$(DefineConstants);FEATURE_MANAGED_ETW_CHANNELS</DefineConstants>
<DefineConstants Condition="'$(FeaturePerfTracing)' == 'true'">$(DefineConstants);FEATURE_PERFTRACING</DefineConstants>
+ <DefineConstants Condition="'$(FeatureObjCMarshal)' == 'true'">$(DefineConstants);FEATURE_OBJCMARSHAL</DefineConstants>
</PropertyGroup>
<!-- Experimental mono metadata update feature -->
<ItemGroup>
<ILLinkDescriptorsXmls Include="$(ILLinkDirectory)ILLink.Descriptors.xml" />
+ <ILLinkDescriptorsXmls Include="$(ILLinkDirectory)ILLink.Descriptors.OSX.xml"
+ Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'" />
<ILLinkDescriptorsXmls Include="$(CoreLibSharedDir)ILLink\ILLink.Descriptors.Shared.xml" />
<ILLinkSubstitutionsXmls Include="$(ILLinkDirectory)ILLink.Substitutions.xml" />
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<linker>
+ <assembly fullname="System.Private.CoreLib">
+ <type fullname="System.Threading.AutoreleasePool">
+ <!-- threads.c -->
+ <method name="CreateAutoreleasePool" />
+ <method name="DrainAutoreleasePool" />
+ </type>
+ </assembly>
+</linker>
\ No newline at end of file
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;
/* 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");
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);
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 */
}
}
+ /* 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);
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;
thread->apartment_state = ThreadApartmentState_MTA;
}
mono_thread_init_apartment_state ();
-
+ mono_thread_init_from_native ();
}
static int
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*
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);
#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)
{
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This is here because of this bug: https://docs.microsoft.com/en-us/archive/blogs/msbuild/well-known-limitation-dynamic-items-and-properties-not-emitted-until-target-execution-completes -->
- <Target Name="FetchExternalPropertiesForXpalt">
+ <Target Name="FetchExternalPropertiesForXplat">
<!--Call GetExecuteShFullPath to get ToRunProject cmd file Path -->
<MSBuild Projects="$(CLRTestProjectToRun)"
Targets="GetExecuteShFullPath"
<Target Name="GenerateBashExecutionScript"
Inputs="$(MSBuildProjectFullPath)"
Outputs="$(OutputPath)\$(MSBuildProjectName).sh"
- DependsOnTargets="FetchExternalPropertiesForXpalt;$(BashScriptSnippetGen);GetIlasmRoundTripBashScript">
+ DependsOnTargets="FetchExternalPropertiesForXplat;$(BashScriptSnippetGen);GetIlasmRoundTripBashScript">
<Message Text="Project depends on $(_CLRTestToRunFileFullPath)." Condition="'$(_CLRTestNeedsProjectToRun)' == 'True'" />
<PropertyGroup>
<IncompatibleTestBashScriptExitCode>0</IncompatibleTestBashScriptExitCode>
+ <CoreRunArgs>@(RuntimeHostConfigurationOption -> '-p "%(Identity)=%(Value)"', ' ')</CoreRunArgs>
<BashCLRTestEnvironmentCompatibilityCheck Condition="'$(GCStressIncompatible)' == 'true'"><![CDATA[
$(BashCLRTestEnvironmentCompatibilityCheck)
</BashLinkerTestCleanupCmds>
</PropertyGroup>
<PropertyGroup>
- <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"$CORE_ROOT/corerun"</_CLRTestRunFile>
+ <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"$CORE_ROOT/corerun" $(CoreRunArgs)</_CLRTestRunFile>
<BashCLRTestPreCommands Condition="'$(CLRTestKind)' == 'BuildAndRun' and '$(TargetArchitecture)' == 'wasm'">
<![CDATA[
<Message Text="Project depends on $(_CLRTestToRunFileFullPath)." Condition="'$(_CLRTestNeedsProjectToRun)' == 'True'" />
<PropertyGroup>
+ <CoreRunArgs>@(RuntimeHostConfigurationOption -> '-p "%(Identity)=%(Value)"', ' ')</CoreRunArgs>
<BatchCLRTestEnvironmentCompatibilityCheck Condition="'$(GCStressIncompatible)' == 'true'"><![CDATA[
$(BatchCLRTestEnvironmentCompatibilityCheck)
IF NOT "%COMPlus_GCStress%"=="" (
</BatchLinkerTestCleanupCmds>
</PropertyGroup>
<PropertyGroup>
- <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"%CORE_ROOT%\corerun.exe"</_CLRTestRunFile>
+ <_CLRTestRunFile Condition="'$(CLRTestIsHosted)'=='true'">"%CORE_ROOT%\corerun.exe" $(CoreRunArgs)</_CLRTestRunFile>
<BatchCopyCoreShimLocalCmds Condition="'$(CLRTestScriptLocalCoreShim)' == 'true'"><![CDATA[
REM Local CoreShim requested - see MSBuild property 'CLRTestScriptLocalCoreShim'
ECHO Copying '%CORE_ROOT%\CoreShim.dll'...
using System.Threading;
using TestLibrary;
-internal static class ObjectiveC
+internal static unsafe class ObjectiveC
{
[DllImport(nameof(ObjectiveC))]
- internal static extern IntPtr initObject();
+ public static extern IntPtr initObject();
[DllImport(nameof(ObjectiveC))]
- internal static extern void autoreleaseObject(IntPtr art);
+ public static extern void autoreleaseObject(IntPtr art);
[DllImport(nameof(ObjectiveC))]
- internal static extern int getNumReleaseCalls();
+ public static extern int getNumReleaseCalls();
}
public class AutoReleaseTest
{
public static int Main()
{
- AppContext.SetSwitch("System.Threading.ThreadPool.EnableDispatchAutoreleasePool", true);
try
{
- TestAutoRelease();
+ ValidateNewManagedThreadAutoRelease();
+ ValidateThreadPoolAutoRelease();
}
catch (Exception e)
{
return 100;
}
- private static void TestAutoRelease()
+ private static void ValidateNewManagedThreadAutoRelease()
{
+ Console.WriteLine($"Running {nameof(ValidateNewManagedThreadAutoRelease)}...");
+ using (AutoResetEvent evt = new AutoResetEvent(false))
+ {
+ int numReleaseCalls = ObjectiveC.getNumReleaseCalls();
+
+ RunScenario(evt);
+
+ // Trigger the GC and wait to clean up the allocated managed Thread instance.
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ Assert.AreEqual(numReleaseCalls + 1, ObjectiveC.getNumReleaseCalls());
+ }
+
+ static void RunScenario(AutoResetEvent evt)
+ {
+ IntPtr obj = ObjectiveC.initObject();
+ var thread = new Thread(_ =>
+ {
+ 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();
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CLRTestTargetUnsupported Condition="'$(TargetsOSX)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'">true</CLRTestTargetUnsupported>
</PropertyGroup>
+ <ItemGroup>
+ <RuntimeHostConfigurationOption Include="System.Threading.Thread.EnableAutoreleasePool" Value="true" Trim="true" />
+ </ItemGroup>
<ItemGroup>
<Compile Include="*.cs" />
</ItemGroup>