From e64cbb5c722ab9b7d93ca32445e17df1115be70c Mon Sep 17 00:00:00 2001 From: Sean Gillespie Date: Thu, 9 Nov 2017 09:58:47 -0800 Subject: [PATCH] [Local GC] Unify background GC thread and server GC thread creation (#14821) * Initial cut, ignoring thread affinity * Integrate thread affinity * Affinity for standalone Windows * Add 'specialness' and the thread name as arguments to CreateThread * First crack at unified implementation * Set priority for server GC threads * Remove unused parameter * Address code review feedback and remove some dead code that broke the clang build * Use char* on the interface instead of wchar_t (doesn't play well cross-platform) * Rename IsGCSpecialThread -> CurrentThreadWasCreatedByGC * Code review feedback and fix up the build * rename CurrentThreadWasCreatedByGC -> WasCurrentThreadCreatedByGC * Fix a contract violation when converting string encodings * Thread::CreateUtilityThread returns a thread that is not suspended - restarting a non-suspended thread is incorrect * CreateUnsuspendableThread -> CreateNonSuspendableThread --- src/gc/env/gcenv.ee.h | 6 +- src/gc/env/gcenv.os.h | 26 ++-- src/gc/gc.cpp | 73 ++++----- src/gc/gcenv.ee.standalone.inl | 17 ++- src/gc/gcinterface.ee.h | 35 +++-- src/gc/gcpriv.h | 4 +- src/gc/sample/gcenv.ee.cpp | 13 +- src/gc/unix/gcenv.unix.cpp | 87 +++-------- src/gc/windows/gcenv.windows.cpp | 104 ++++++------- src/vm/gcenv.ee.cpp | 252 +++++++++++++++++++++++-------- src/vm/gcenv.ee.h | 4 +- src/vm/gcenv.os.cpp | 123 ++++++--------- 12 files changed, 394 insertions(+), 350 deletions(-) diff --git a/src/gc/env/gcenv.ee.h b/src/gc/env/gcenv.ee.h index 3d2f659a7502..d747a5bbefe1 100644 --- a/src/gc/env/gcenv.ee.h +++ b/src/gc/env/gcenv.ee.h @@ -56,9 +56,6 @@ class GCToEEInterface static bool CatchAtSafePoint(Thread * pThread); static void GcEnumAllocContexts(enum_alloc_context_func* fn, void* param); - - static Thread* CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg); - // Diagnostics methods. static void DiagGCStart(int gen, bool isInduced); static void DiagUpdateGenerationBounds(); @@ -81,7 +78,8 @@ class GCToEEInterface static bool GetStringConfigValue(const char* key, const char** value); static void FreeStringConfigValue(const char* key); static bool IsGCThread(); - static bool IsGCSpecialThread(); + static bool WasCurrentThreadCreatedByGC(); + static bool CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name); }; #endif // __GCENV_EE_H__ diff --git a/src/gc/env/gcenv.os.h b/src/gc/env/gcenv.os.h index aee1330f3fb2..1707f0dabec7 100644 --- a/src/gc/env/gcenv.os.h +++ b/src/gc/env/gcenv.os.h @@ -243,15 +243,6 @@ class GCToOSInterface // Thread and process // - // Create a new thread - // Parameters: - // function - the function to be executed by the thread - // param - parameters of the thread - // affinity - processor affinity of the thread - // Return: - // true if it has succeeded, false if it has failed - static bool CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity); - // Causes the calling thread to sleep for the specified number of milliseconds // Parameters: // sleepMSec - time to sleep before switching to another thread @@ -307,6 +298,23 @@ class GCToOSInterface // The number of processors static uint32_t GetCurrentProcessCpuCount(); + // Sets the calling thread's affinity to only run on the processor specified + // in the GCThreadAffinity structure. + // Parameters: + // affinity - The requested affinity for the calling thread. At most one processor + // can be provided. + // Return: + // true if setting the affinity was successful, false otherwise. + static bool SetThreadAffinity(GCThreadAffinity* affinity); + + // Boosts the calling thread's thread priority to a level higher than the default + // for new threads. + // Parameters: + // None. + // Return: + // true if the priority boost was successful, false otherwise. + static bool BoostThreadPriority(); + // Get affinity mask of the current process // Parameters: // processMask - affinity mask for the specified process diff --git a/src/gc/gc.cpp b/src/gc/gc.cpp index 042f2f49295e..445812cd4081 100644 --- a/src/gc/gc.cpp +++ b/src/gc/gc.cpp @@ -1740,7 +1740,7 @@ static BOOL try_enter_spin_lock(GCSpinLock *pSpinLock) inline static void leave_spin_lock(GCSpinLock *pSpinLock) { - bool gc_thread_p = GCToEEInterface::IsGCSpecialThread(); + bool gc_thread_p = GCToEEInterface::WasCurrentThreadCreatedByGC(); // _ASSERTE((pSpinLock->holding_thread == GCToEEInterface::GetThread()) || gc_thread_p || pSpinLock->released_by_gc_p); pSpinLock->released_by_gc_p = gc_thread_p; pSpinLock->holding_thread = (Thread*) -1; @@ -5351,23 +5351,7 @@ void set_thread_affinity_mask_for_heap(int heap_number, GCThreadAffinity* affini bool gc_heap::create_gc_thread () { dprintf (3, ("Creating gc thread\n")); - - GCThreadAffinity affinity; - affinity.Group = GCThreadAffinity::None; - affinity.Processor = GCThreadAffinity::None; - - if (!gc_thread_no_affinitize_p) - { - // We are about to set affinity for GC threads. It is a good place to set up NUMA and - // CPU groups because the process mask, processor number, and group number are all - // readily available. - if (CPUGroupInfo::CanEnableGCCPUGroups()) - set_thread_group_affinity_for_heap(heap_number, &affinity); - else - set_thread_affinity_mask_for_heap(heap_number, &affinity); - } - - return GCToOSInterface::CreateThread(gc_thread_stub, this, &affinity); + return GCToEEInterface::CreateThread(gc_thread_stub, this, false, "Server GC"); } #ifdef _MSC_VER @@ -24917,27 +24901,29 @@ void gc_heap::compact_phase (int condemned_gen_number, #endif //_MSC_VER void gc_heap::gc_thread_stub (void* arg) { - ClrFlsSetThreadType (ThreadType_GC); - STRESS_LOG_RESERVE_MEM (GC_STRESSLOG_MULTIPLY); + gc_heap* heap = (gc_heap*)arg; + if (!gc_thread_no_affinitize_p) + { + GCThreadAffinity affinity; + affinity.Group = GCThreadAffinity::None; + affinity.Processor = GCThreadAffinity::None; -#ifndef FEATURE_REDHAWK - // We commit the thread's entire stack to ensure we're robust in low memory conditions. - BOOL fSuccess = Thread::CommitThreadStack(NULL); + // We are about to set affinity for GC threads. It is a good place to set up NUMA and + // CPU groups because the process mask, processor number, and group number are all + // readily available. + if (CPUGroupInfo::CanEnableGCCPUGroups()) + set_thread_group_affinity_for_heap(heap->heap_number, &affinity); + else + set_thread_affinity_mask_for_heap(heap->heap_number, &affinity); - if (!fSuccess) - { -#ifdef BACKGROUND_GC - // For background GC we revert to doing a blocking GC. - return; -#else - STRESS_LOG0(LF_GC, LL_ALWAYS, "Thread::CommitThreadStack failed."); - _ASSERTE(!"Thread::CommitThreadStack failed."); - GCToEEInterface::HandleFatalError(COR_E_STACKOVERFLOW); -#endif //BACKGROUND_GC + if (!GCToOSInterface::SetThreadAffinity(&affinity)) + { + dprintf(1, ("Failed to set thread affinity for server GC thread")); + } } -#endif // FEATURE_REDHAWK - gc_heap* heap = (gc_heap*)arg; + // server GC threads run at a higher priority than normal. + GCToOSInterface::BoostThreadPriority(); _alloca (256*heap->heap_number); heap->gc_thread_function(); } @@ -24953,10 +24939,12 @@ void gc_heap::gc_thread_stub (void* arg) #pragma warning(push) #pragma warning(disable:4702) // C4702: unreachable code: gc_thread_function may not return #endif //_MSC_VER -uint32_t __stdcall gc_heap::bgc_thread_stub (void* arg) +void gc_heap::bgc_thread_stub (void* arg) { gc_heap* heap = (gc_heap*)arg; - return heap->bgc_thread_function(); + heap->bgc_thread = GCToEEInterface::GetThread(); + assert(heap->bgc_thread != nullptr); + heap->bgc_thread_function(); } #ifdef _MSC_VER #pragma warning(pop) @@ -26700,7 +26688,6 @@ BOOL gc_heap::prepare_bgc_thread(gc_heap* gh) BOOL success = FALSE; BOOL thread_created = FALSE; dprintf (2, ("Preparing gc thread")); - gh->bgc_threads_timeout_cs.Enter(); if (!(gh->bgc_thread_running)) { @@ -26730,9 +26717,7 @@ BOOL gc_heap::create_bgc_thread(gc_heap* gh) //dprintf (2, ("Creating BGC thread")); - gh->bgc_thread = GCToEEInterface::CreateBackgroundThread(gh->bgc_thread_stub, gh); - gh->bgc_thread_running = (gh->bgc_thread != NULL); - + gh->bgc_thread_running = GCToEEInterface::CreateThread(gh->bgc_thread_stub, gh, true, "Background GC"); return gh->bgc_thread_running; } @@ -26908,7 +26893,7 @@ void gc_heap::kill_gc_thread() recursive_gc_sync::shutdown(); } -uint32_t gc_heap::bgc_thread_function() +void gc_heap::bgc_thread_function() { assert (background_gc_done_event.IsValid()); assert (bgc_start_event.IsValid()); @@ -27066,7 +27051,7 @@ uint32_t gc_heap::bgc_thread_function() FireEtwGCTerminateConcurrentThread_V1(GetClrInstanceId()); dprintf (3, ("bgc_thread thread exiting")); - return 0; + return; } #endif //BACKGROUND_GC @@ -34028,7 +34013,7 @@ bool GCHeap::StressHeap(gc_alloc_context * context) #ifdef BACKGROUND_GC // don't trigger a GC from the GC threads but still trigger GCs from user threads. - if (GCToEEInterface::IsGCSpecialThread()) + if (GCToEEInterface::WasCurrentThreadCreatedByGC()) { return FALSE; } diff --git a/src/gc/gcenv.ee.standalone.inl b/src/gc/gcenv.ee.standalone.inl index 0b6a1cfb117f..a9e45c953bc7 100644 --- a/src/gc/gcenv.ee.standalone.inl +++ b/src/gc/gcenv.ee.standalone.inl @@ -132,12 +132,6 @@ inline void GCToEEInterface::GcEnumAllocContexts(enum_alloc_context_func* fn, vo g_theGCToCLR->GcEnumAllocContexts(fn, param); } -inline Thread* GCToEEInterface::CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) -{ - assert(g_theGCToCLR != nullptr); - return g_theGCToCLR->CreateBackgroundThread(threadStart, arg); -} - inline void GCToEEInterface::DiagGCStart(int gen, bool isInduced) { assert(g_theGCToCLR != nullptr); @@ -252,10 +246,17 @@ inline bool GCToEEInterface::IsGCThread() return g_theGCToCLR->IsGCThread(); } -inline bool GCToEEInterface::IsGCSpecialThread() +inline bool GCToEEInterface::WasCurrentThreadCreatedByGC() { assert(g_theGCToCLR != nullptr); - return g_theGCToCLR->IsGCSpecialThread(); + return g_theGCToCLR->WasCurrentThreadCreatedByGC(); } +inline bool GCToEEInterface::CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name) +{ + assert(g_theGCToCLR != nullptr); + return g_theGCToCLR->CreateThread(threadStart, arg, is_suspendable, name); +} + + #endif // __GCTOENV_EE_STANDALONE_INL__ diff --git a/src/gc/gcinterface.ee.h b/src/gc/gcinterface.ee.h index d65da60678f2..84578b6d8a5c 100644 --- a/src/gc/gcinterface.ee.h +++ b/src/gc/gcinterface.ee.h @@ -79,6 +79,9 @@ class IGCToCLR { // Gets the Thread instance for the current thread, or null if no thread // instance is associated with this thread. + // + // If the GC created the current thread, GetThread returns null for threads + // that were not created as suspendable (see `IGCHeap::CreateThread`). virtual Thread* GetThread() = 0; @@ -98,9 +101,21 @@ class IGCToCLR { virtual void GcEnumAllocContexts(enum_alloc_context_func* fn, void* param) = 0; - // Creates and returns a new background thread. - virtual - Thread* CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) = 0; + // Creates and returns a new thread. + // Parameters: + // threadStart - The function that will serve as the thread stub for the + // new thread. It will be invoked immediately upon the + // new thread upon creation. + // arg - The argument that will be passed verbatim to threadStart. + // is_suspendable - Whether or not the thread that is created should be suspendable + // from a runtime perspective. Threads that are suspendable have + // a VM Thread object associated with them that can be accessed + // using `IGCHeap::GetThread`. + // name - The name of this thread, optionally used for diagnostic purposes. + // Returns: + // true if the thread was started successfully, false if not. + virtual + bool CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name) = 0; // When a GC starts, gives the diagnostics code a chance to run. virtual @@ -192,16 +207,18 @@ class IGCToCLR { virtual void FreeStringConfigValue(const char* value) = 0; - // Asks the EE about whether or not the current thread is a GC thread: - // a server GC thread, background GC thread, or the thread that suspended - // the EE at the start of a GC. + // Returns true if this thread is a "GC thread", or a thread capable of + // doing GC work. Threads are either /always/ GC threads + // (if they were created for this purpose - background GC threads + // and server GC threads) or they became GC threads by suspending the EE + // and initiating a collection. virtual bool IsGCThread() = 0; - // Asks the EE about whether or not the current thread is a GC "special" - // thread: a server GC thread or a background GC thread. + // Returns true if the current thread is either a background GC thread + // or a server GC thread. virtual - bool IsGCSpecialThread() = 0; + bool WasCurrentThreadCreatedByGC() = 0; }; #endif // _GCINTERFACE_EE_H_ diff --git a/src/gc/gcpriv.h b/src/gc/gcpriv.h index c9c6fa32f908..470b82f2ef8d 100644 --- a/src/gc/gcpriv.h +++ b/src/gc/gcpriv.h @@ -2671,11 +2671,11 @@ class gc_heap PER_HEAP void kill_gc_thread(); PER_HEAP - uint32_t bgc_thread_function(); + void bgc_thread_function(); PER_HEAP_ISOLATED void do_background_gc(); static - uint32_t __stdcall bgc_thread_stub (void* arg); + void bgc_thread_stub (void* arg); #endif //BACKGROUND_GC diff --git a/src/gc/sample/gcenv.ee.cpp b/src/gc/sample/gcenv.ee.cpp index 8fd448719238..72ef9b55744f 100644 --- a/src/gc/sample/gcenv.ee.cpp +++ b/src/gc/sample/gcenv.ee.cpp @@ -231,12 +231,6 @@ void GCToEEInterface::SyncBlockCachePromotionsGranted(int /*max_gen*/) { } -Thread* GCToEEInterface::CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) -{ - // TODO: Implement for background GC - return NULL; -} - void GCToEEInterface::DiagGCStart(int gen, bool isInduced) { } @@ -321,7 +315,7 @@ bool GCToEEInterface::IsGCThread() return false; } -bool GCToEEInterface::IsGCSpecialThread() +bool GCToEEInterface::WasCurrentThreadCreatedByGC() { return false; } @@ -330,3 +324,8 @@ MethodTable* GCToEEInterface::GetFreeObjectMethodTable() { return g_pFreeObjectMethodTable; } + +bool GCToEEInterface::CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name) +{ + return false; +} diff --git a/src/gc/unix/gcenv.unix.cpp b/src/gc/unix/gcenv.unix.cpp index f564b2823909..75428c470797 100644 --- a/src/gc/unix/gcenv.unix.cpp +++ b/src/gc/unix/gcenv.unix.cpp @@ -402,6 +402,31 @@ size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize) return 0; } +// Sets the calling thread's affinity to only run on the processor specified +// in the GCThreadAffinity structure. +// Parameters: +// affinity - The requested affinity for the calling thread. At most one processor +// can be provided. +// Return: +// true if setting the affinity was successful, false otherwise. +bool GCToOSInterface::SetThreadAffinity(GCThreadAffinity* affinity) +{ + // [LOCALGC TODO] Thread affinity for unix + return false; +} + +// Boosts the calling thread's thread priority to a level higher than the default +// for new threads. +// Parameters: +// None. +// Return: +// true if the priority boost was successful, false otherwise. +bool GCToOSInterface::BoostThreadPriority() +{ + // [LOCALGC TODO] Thread priority for unix + return false; +} + /*++ Function: GetFullAffinityMask @@ -654,68 +679,6 @@ uint32_t GCToOSInterface::GetLowPrecisionTimeStamp() return retval; } -// Parameters of the GC thread stub -struct GCThreadStubParam -{ - GCThreadFunction GCThreadFunction; - void* GCThreadParam; -}; - -// GC thread stub to convert GC thread function to an OS specific thread function -static void* GCThreadStub(void* param) -{ - GCThreadStubParam *stubParam = (GCThreadStubParam*)param; - GCThreadFunction function = stubParam->GCThreadFunction; - void* threadParam = stubParam->GCThreadParam; - - delete stubParam; - - function(threadParam); - - return NULL; -} - -// Create a new thread for GC use -// Parameters: -// function - the function to be executed by the thread -// param - parameters of the thread -// affinity - processor affinity of the thread -// Return: -// true if it has succeeded, false if it has failed -bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity) -{ - std::unique_ptr stubParam(new (std::nothrow) GCThreadStubParam()); - if (!stubParam) - { - return false; - } - - stubParam->GCThreadFunction = function; - stubParam->GCThreadParam = param; - - pthread_attr_t attrs; - - int st = pthread_attr_init(&attrs); - assert(st == 0); - - // Create the thread as detached, that means not joinable - st = pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED); - assert(st == 0); - - pthread_t threadId; - st = pthread_create(&threadId, &attrs, GCThreadStub, stubParam.get()); - - if (st == 0) - { - stubParam.release(); - } - - int st2 = pthread_attr_destroy(&attrs); - assert(st2 == 0); - - return (st == 0); -} - // Gets the total number of processors on the machine, not taking // into account current process affinity. // Return: diff --git a/src/gc/windows/gcenv.windows.cpp b/src/gc/windows/gcenv.windows.cpp index e05233fe7c77..69e5d7273aab 100644 --- a/src/gc/windows/gcenv.windows.cpp +++ b/src/gc/windows/gcenv.windows.cpp @@ -387,6 +387,48 @@ size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize) return 0; } +// Sets the calling thread's affinity to only run on the processor specified +// in the GCThreadAffinity structure. +// Parameters: +// affinity - The requested affinity for the calling thread. At most one processor +// can be provided. +// Return: +// true if setting the affinity was successful, false otherwise. +bool GCToOSInterface::SetThreadAffinity(GCThreadAffinity* affinity) +{ + assert(affinity != nullptr); + if (affinity->Group != GCThreadAffinity::None) + { + assert(affinity->Processor != GCThreadAffinity::None); + + GROUP_AFFINITY ga; + ga.Group = (WORD)affinity->Group; + ga.Reserved[0] = 0; // reserve must be filled with zero + ga.Reserved[1] = 0; // otherwise call may fail + ga.Reserved[2] = 0; + ga.Mask = (size_t)1 << affinity->Processor; + return !!SetThreadGroupAffinity(GetCurrentThread(), &ga, nullptr); + } + else if (affinity->Processor != GCThreadAffinity::None) + { + return !!SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1 << affinity->Processor); + } + + // Given affinity must specify at least one processor to use. + return false; +} + +// Boosts the calling thread's thread priority to a level higher than the default +// for new threads. +// Parameters: +// None. +// Return: +// true if the priority boost was successful, false otherwise. +bool GCToOSInterface::BoostThreadPriority() +{ + return !!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); +} + // Get affinity mask of the current process // Parameters: // processMask - affinity mask for the specified process @@ -545,68 +587,6 @@ static DWORD GCThreadStub(void* param) return 0; } - -// Create a new thread for GC use -// Parameters: -// function - the function to be executed by the thread -// param - parameters of the thread -// affinity - processor affinity of the thread -// Return: -// true if it has succeeded, false if it has failed -bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity) -{ - uint32_t thread_id; - - std::unique_ptr stubParam(new (std::nothrow) GCThreadStubParam()); - if (!stubParam) - { - return false; - } - - stubParam->GCThreadFunction = function; - stubParam->GCThreadParam = param; - - HANDLE gc_thread = ::CreateThread( - nullptr, - 512 * 1024 /* Thread::StackSize_Medium */, - (LPTHREAD_START_ROUTINE)GCThreadStub, - stubParam.get(), - CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, - (DWORD*)&thread_id); - - if (!gc_thread) - { - return false; - } - - stubParam.release(); - bool result = !!::SetThreadPriority(gc_thread, /* THREAD_PRIORITY_ABOVE_NORMAL );*/ THREAD_PRIORITY_HIGHEST ); - assert(result && "failed to set thread priority"); - - if (affinity->Group != GCThreadAffinity::None) - { - assert(affinity->Processor != GCThreadAffinity::None); - GROUP_AFFINITY ga; - ga.Group = (WORD)affinity->Group; - ga.Reserved[0] = 0; // reserve must be filled with zero - ga.Reserved[1] = 0; // otherwise call may fail - ga.Reserved[2] = 0; - ga.Mask = (size_t)1 << affinity->Processor; - - bool result = !!::SetThreadGroupAffinity(gc_thread, &ga, nullptr); - assert(result && "failed to set thread affinity"); - } - else if (affinity->Processor != GCThreadAffinity::None) - { - ::SetThreadAffinityMask(gc_thread, (DWORD_PTR)1 << affinity->Processor); - } - - ResumeThread(gc_thread); - CloseHandle(gc_thread); - - return true; -} - // Gets the total number of processors on the machine, not taking // into account current process affinity. // Return: diff --git a/src/vm/gcenv.ee.cpp b/src/vm/gcenv.ee.cpp index 959687b7b85a..e5da889a64f1 100644 --- a/src/vm/gcenv.ee.cpp +++ b/src/vm/gcenv.ee.cpp @@ -409,67 +409,6 @@ DWORD WINAPI BackgroundThreadStub(void* arg) return result; } -Thread* GCToEEInterface::CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - } - CONTRACTL_END; - - BackgroundThreadStubArgs threadStubArgs; - - threadStubArgs.arg = arg; - threadStubArgs.thread = NULL; - threadStubArgs.threadStart = threadStart; - threadStubArgs.hasStarted = false; - - if (!threadStubArgs.threadStartedEvent.CreateAutoEventNoThrow(FALSE)) - { - return NULL; - } - - EX_TRY - { - threadStubArgs.thread = SetupUnstartedThread(FALSE); - } - EX_CATCH - { - } - EX_END_CATCH(SwallowAllExceptions); - - if (threadStubArgs.thread == NULL) - { - threadStubArgs.threadStartedEvent.CloseEvent(); - return NULL; - } - - if (threadStubArgs.thread->CreateNewThread(0, (LPTHREAD_START_ROUTINE)BackgroundThreadStub, &threadStubArgs, W("Background GC"))) - { - threadStubArgs.thread->SetBackground (TRUE, FALSE); - threadStubArgs.thread->StartThread(); - - // Wait for the thread to be in its main loop - uint32_t res = threadStubArgs.threadStartedEvent.Wait(INFINITE, FALSE); - threadStubArgs.threadStartedEvent.CloseEvent(); - _ASSERTE(res == WAIT_OBJECT_0); - - if (!threadStubArgs.hasStarted) - { - // The thread has failed to start and the Thread object was destroyed in the Thread::HasStarted - // failure code path. - return NULL; - } - - return threadStubArgs.thread; - } - - // Destroy the Thread object - threadStubArgs.thread->DecExternalCount(FALSE); - return NULL; -} - // // Diagnostics code // @@ -1175,7 +1114,196 @@ bool GCToEEInterface::IsGCThread() return !!::IsGCThread(); } -bool GCToEEInterface::IsGCSpecialThread() +bool GCToEEInterface::WasCurrentThreadCreatedByGC() { return !!::IsGCSpecialThread(); } + +struct SuspendableThreadStubArguments +{ + void* Argument; + void (*ThreadStart)(void*); + Thread* Thread; + bool HasStarted; + CLREvent ThreadStartedEvent; +}; + +struct ThreadStubArguments +{ + void* Argument; + void (*ThreadStart)(void*); + HANDLE Thread; + bool HasStarted; + CLREvent ThreadStartedEvent; +}; + +namespace +{ + const size_t MaxThreadNameSize = 255; + + bool CreateSuspendableThread( + void (*threadStart)(void*), + void* argument, + const char* name) + { + LIMITED_METHOD_CONTRACT; + + SuspendableThreadStubArguments args; + args.Argument = argument; + args.ThreadStart = threadStart; + args.Thread = nullptr; + args.HasStarted = false; + if (!args.ThreadStartedEvent.CreateAutoEventNoThrow(FALSE)) + { + return false; + } + + EX_TRY + { + args.Thread = SetupUnstartedThread(FALSE); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions) + + if (!args.Thread) + { + args.ThreadStartedEvent.CloseEvent(); + return false; + } + + auto threadStub = [](void* argument) -> DWORD + { + SuspendableThreadStubArguments* args = static_cast(argument); + assert(args != nullptr); + + ClrFlsSetThreadType(ThreadType_GC); + args->Thread->SetGCSpecial(true); + STRESS_LOG_RESERVE_MEM(GC_STRESSLOG_MULTIPLY); + args->HasStarted = !!args->Thread->HasStarted(false); + + Thread* thread = args->Thread; + auto threadStart = args->ThreadStart; + void* threadArgument = args->Argument; + bool hasStarted = args->HasStarted; + args->ThreadStartedEvent.Set(); + + // The stubArgs cannot be used once the event is set, since that releases wait on the + // event in the function that created this thread and the stubArgs go out of scope. + if (hasStarted) + { + threadStart(threadArgument); + DestroyThread(thread); + } + + return 0; + }; + + InlineSString wideName; + const WCHAR* namePtr; + EX_TRY + { + if (name != nullptr) + { + wideName.SetUTF8(name); + namePtr = wideName.GetUnicode(); + } + } + EX_CATCH + { + // we're not obligated to provide a name - if it's not valid, + // just report nullptr as the name. + namePtr = nullptr; + } + EX_END_CATCH(SwallowAllExceptions) + + if (!args.Thread->CreateNewThread(0, threadStub, &args, namePtr)) + { + args.Thread->DecExternalCount(FALSE); + args.ThreadStartedEvent.CloseEvent(); + return false; + } + + args.Thread->SetBackground(TRUE, FALSE); + args.Thread->StartThread(); + + // Wait for the thread to be in its main loop + uint32_t res = args.ThreadStartedEvent.Wait(INFINITE, FALSE); + args.ThreadStartedEvent.CloseEvent(); + _ASSERTE(res == WAIT_OBJECT_0); + + if (!args.HasStarted) + { + // The thread has failed to start and the Thread object was destroyed in the Thread::HasStarted + // failure code path. + return false; + } + + return true; + } + + bool CreateNonSuspendableThread( + void (*threadStart)(void*), + void* argument, + const char* name) + { + LIMITED_METHOD_CONTRACT; + + ThreadStubArguments args; + args.Argument = argument; + args.ThreadStart = threadStart; + args.Thread = INVALID_HANDLE_VALUE; + if (!args.ThreadStartedEvent.CreateAutoEventNoThrow(FALSE)) + { + return false; + } + + auto threadStub = [](void* argument) -> DWORD + { + ThreadStubArguments* args = static_cast(argument); + assert(args != nullptr); + + ClrFlsSetThreadType(ThreadType_GC); + STRESS_LOG_RESERVE_MEM(GC_STRESSLOG_MULTIPLY); + + args->HasStarted = true; + auto threadStart = args->ThreadStart; + void* threadArgument = args->Argument; + args->ThreadStartedEvent.Set(); + + // The stub args cannot be used once the event is set, since that releases wait on the + // event in the function that created this thread and the stubArgs go out of scope. + threadStart(threadArgument); + return 0; + }; + + args.Thread = Thread::CreateUtilityThread(Thread::StackSize_Medium, threadStub, &args); + if (args.Thread == INVALID_HANDLE_VALUE) + { + args.ThreadStartedEvent.CloseEvent(); + return false; + } + + // Wait for the thread to be in its main loop + uint32_t res = args.ThreadStartedEvent.Wait(INFINITE, FALSE); + args.ThreadStartedEvent.CloseEvent(); + _ASSERTE(res == WAIT_OBJECT_0); + + CloseHandle(args.Thread); + return true; + } +} // anonymous namespace + +bool GCToEEInterface::CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name) +{ + LIMITED_METHOD_CONTRACT; + if (is_suspendable) + { + return CreateSuspendableThread(threadStart, arg, name); + } + else + { + return CreateNonSuspendableThread(threadStart, arg, name); + } +} diff --git a/src/vm/gcenv.ee.h b/src/vm/gcenv.ee.h index 063fa2554e54..b2ada36bcda3 100644 --- a/src/vm/gcenv.ee.h +++ b/src/vm/gcenv.ee.h @@ -36,7 +36,6 @@ class GCToEEInterface : public IGCToCLR { gc_alloc_context * GetAllocContext(Thread * pThread); bool CatchAtSafePoint(Thread * pThread); void GcEnumAllocContexts(enum_alloc_context_func* fn, void* param); - Thread* CreateBackgroundThread(GCBackgroundThreadFunction threadStart, void* arg); // Diagnostics methods. void DiagGCStart(int gen, bool isInduced); @@ -59,7 +58,8 @@ class GCToEEInterface : public IGCToCLR { bool GetStringConfigValue(const char* key, const char** value); void FreeStringConfigValue(const char* value); bool IsGCThread(); - bool IsGCSpecialThread(); + bool WasCurrentThreadCreatedByGC(); + bool CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name); }; } // namespace standalone diff --git a/src/vm/gcenv.os.cpp b/src/vm/gcenv.os.cpp index b1b9c326351f..78670b0af3d3 100644 --- a/src/vm/gcenv.os.cpp +++ b/src/vm/gcenv.os.cpp @@ -329,6 +329,50 @@ size_t GCToOSInterface::GetLargestOnDieCacheSize(bool trueSize) return ::GetLargestOnDieCacheSize(trueSize); } +// Sets the calling thread's affinity to only run on the processor specified +// in the GCThreadAffinity structure. +// Parameters: +// affinity - The requested affinity for the calling thread. At most one processor +// can be provided. +// Return: +// true if setting the affinity was successful, false otherwise. +bool GCToOSInterface::SetThreadAffinity(GCThreadAffinity* affinity) +{ + LIMITED_METHOD_CONTRACT; + + assert(affinity != nullptr); + if (affinity->Group != GCThreadAffinity::None) + { + assert(affinity->Processor != GCThreadAffinity::None); + + GROUP_AFFINITY ga; + ga.Group = (WORD)affinity->Group; + ga.Reserved[0] = 0; // reserve must be filled with zero + ga.Reserved[1] = 0; // otherwise call may fail + ga.Reserved[2] = 0; + ga.Mask = (size_t)1 << affinity->Processor; + return !!SetThreadGroupAffinity(GetCurrentThread(), &ga, nullptr); + } + else if (affinity->Processor != GCThreadAffinity::None) + { + return !!SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1 << affinity->Processor); + } + + // Given affinity must specify at least one processor to use. + return false; +} + +// Boosts the calling thread's thread priority to a level higher than the default +// for new threads. +// Parameters: +// None. +// Return: +// true if the priority boost was successful, false otherwise. +bool GCToOSInterface::BoostThreadPriority() +{ + return !!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); +} + // Get affinity mask of the current process // Parameters: // processMask - affinity mask for the specified process @@ -605,85 +649,6 @@ uint32_t GCToOSInterface::GetLowPrecisionTimeStamp() return ::GetTickCount(); } -// Parameters of the GC thread stub -struct GCThreadStubParam -{ - GCThreadFunction GCThreadFunction; - void* GCThreadParam; -}; - -// GC thread stub to convert GC thread function to an OS specific thread function -static DWORD WINAPI GCThreadStub(void* param) -{ - WRAPPER_NO_CONTRACT; - - GCThreadStubParam *stubParam = (GCThreadStubParam*)param; - GCThreadFunction function = stubParam->GCThreadFunction; - void* threadParam = stubParam->GCThreadParam; - - delete stubParam; - - function(threadParam); - - return 0; -} - -// Create a new thread -// Parameters: -// function - the function to be executed by the thread -// param - parameters of the thread -// affinity - processor affinity of the thread -// Return: -// true if it has succeeded, false if it has failed -bool GCToOSInterface::CreateThread(GCThreadFunction function, void* param, GCThreadAffinity* affinity) -{ - LIMITED_METHOD_CONTRACT; - - uint32_t thread_id; - - NewHolder stubParam = new (nothrow) GCThreadStubParam(); - if (stubParam == NULL) - { - return false; - } - - stubParam->GCThreadFunction = function; - stubParam->GCThreadParam = param; - - HANDLE gc_thread = Thread::CreateUtilityThread(Thread::StackSize_Medium, GCThreadStub, stubParam, CREATE_SUSPENDED, (DWORD*)&thread_id); - - if (!gc_thread) - { - return false; - } - - stubParam.SuppressRelease(); - - SetThreadPriority(gc_thread, /* THREAD_PRIORITY_ABOVE_NORMAL );*/ THREAD_PRIORITY_HIGHEST ); - - if (affinity->Group != GCThreadAffinity::None) - { - _ASSERTE(affinity->Processor != GCThreadAffinity::None); - GROUP_AFFINITY ga; - ga.Group = (WORD)affinity->Group; - ga.Reserved[0] = 0; // reserve must be filled with zero - ga.Reserved[1] = 0; // otherwise call may fail - ga.Reserved[2] = 0; - ga.Mask = (size_t)1 << affinity->Processor; - - CPUGroupInfo::SetThreadGroupAffinity(gc_thread, &ga, NULL); - } - else if (affinity->Processor != GCThreadAffinity::None) - { - SetThreadAffinityMask(gc_thread, (DWORD_PTR)1 << affinity->Processor); - } - - ResumeThread(gc_thread); - CloseHandle(gc_thread); - - return true; -} - uint32_t GCToOSInterface::GetTotalProcessorCount() { LIMITED_METHOD_CONTRACT;