Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 878cd5d

Browse files
authored
Fix GC background thread start in OOM (#5840)
This change fixes a problem that happened when the `gc_heap::create_bgc_thread` calls SetupUnstartedThread and it fails to allocate a Thread object and throws. The GC doesn't expect that and so when the stack is unwound, the `gc_heap::gc_started` stays set. Now we try to allocate memory for the Throwable for the exception, so we go to GC heap and since there is not enough space, we end up calling `gc_heap::try_allocate_more_space`, which calls `gc_heap::wait_for_gc_done`. And that’s the end of it, since the `gc_started` is still set and we wait forever. The fix is to catch exception from the SetupUnstartedThread. I have also fixed couple of places where this method was being called and the exception was not expected. As an additional cleanup, I have moved the thread creation in GC to the GCToEEInterface.
1 parent b464dc3 commit 878cd5d

File tree

11 files changed

+64
-129
lines changed

11 files changed

+64
-129
lines changed

src/gc/env/gcenv.ee.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ typedef struct
2121
CrawlFrame * cf;
2222
} GCCONTEXT;
2323

24+
// GC background thread function prototype
25+
typedef uint32_t (__stdcall *GCBackgroundThreadFunction)(void* param);
2426

2527
class GCToEEInterface
2628
{
@@ -78,7 +80,7 @@ class GCToEEInterface
7880

7981
static void GcEnumAllocContexts(enum_alloc_context_func* fn, void* param);
8082

81-
static void AttachCurrentThread(); // does not acquire thread store lock
83+
static bool CreateBackgroundThread(Thread** thread, GCBackgroundThreadFunction threadStart, void* arg);
8284
};
8385

8486
#endif // __GCENV_EE_H__

src/gc/gc.cpp

Lines changed: 6 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -26724,55 +26724,11 @@ BOOL gc_heap::prepare_bgc_thread(gc_heap* gh)
2672426724

2672526725
BOOL gc_heap::create_bgc_thread(gc_heap* gh)
2672626726
{
26727-
BOOL ret = FALSE;
26728-
2672926727
assert (background_gc_done_event.IsValid());
2673026728

2673126729
//dprintf (2, ("Creating BGC thread"));
2673226730

26733-
#ifdef FEATURE_REDHAWK
26734-
26735-
// Thread creation is handled a little differently in Redhawk. We create the thread by a call to the OS
26736-
// (via the PAL) and it starts running immediately. We place a wrapper around the start routine to
26737-
// initialize the Redhawk Thread context (since that must be done from the thread itself) and also destroy
26738-
// it as the thread exits. We also set gh->bgc_thread from this wrapper since otherwise we'd have to
26739-
// search the thread store for one with the matching ID. gc->bgc_thread will be valid by the time we've
26740-
// finished the event wait below.
26741-
26742-
rh_bgc_thread_ctx sContext;
26743-
sContext.m_pRealStartRoutine = (PTHREAD_START_ROUTINE)gh->bgc_thread_stub;
26744-
sContext.m_pRealContext = gh;
26745-
26746-
if (!PalStartBackgroundGCThread(gh->rh_bgc_thread_stub, &sContext))
26747-
{
26748-
goto cleanup;
26749-
}
26750-
26751-
#else // FEATURE_REDHAWK
26752-
26753-
Thread* current_bgc_thread;
26754-
26755-
gh->bgc_thread = SetupUnstartedThread(FALSE);
26756-
if (!(gh->bgc_thread))
26757-
{
26758-
goto cleanup;
26759-
}
26760-
26761-
current_bgc_thread = gh->bgc_thread;
26762-
if (!current_bgc_thread->CreateNewThread (0, (DWORD (*)(void*))&(gh->bgc_thread_stub), gh))
26763-
{
26764-
goto cleanup;
26765-
}
26766-
26767-
current_bgc_thread->SetBackground (TRUE, FALSE);
26768-
26769-
// wait for the thread to be in its main loop, this is to detect the situation
26770-
// where someone triggers a GC during dll loading where the loader lock is
26771-
// held.
26772-
current_bgc_thread->StartThread();
26773-
26774-
#endif // FEATURE_REDHAWK
26775-
26731+
if (GCToEEInterface::CreateBackgroundThread(&gh->bgc_thread, gh->bgc_thread_stub, gh))
2677626732
{
2677726733
dprintf (2, ("waiting for the thread to reach its main loop"));
2677826734
// In chk builds this can easily time out since
@@ -26794,19 +26750,17 @@ BOOL gc_heap::create_bgc_thread(gc_heap* gh)
2679426750
}
2679526751
//dprintf (2, ("waiting for the thread to reach its main loop Done."));
2679626752

26797-
ret = TRUE;
26753+
return TRUE;
2679826754
}
2679926755

2680026756
cleanup:
2680126757

26802-
if (!ret)
26758+
if (gh->bgc_thread)
2680326759
{
26804-
if (gh->bgc_thread)
26805-
{
26806-
gh->bgc_thread = 0;
26807-
}
26760+
gh->bgc_thread = 0;
2680826761
}
26809-
return ret;
26762+
26763+
return FALSE;
2681026764
}
2681126765

2681226766
BOOL gc_heap::create_bgc_threads_support (int number_of_heaps)

src/gc/gcee.cpp

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -827,29 +827,6 @@ void GCHeap::DescrGenerationsToProfiler (gen_walk_fn fn, void *context)
827827
}
828828
#endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE)
829829

830-
#if defined(BACKGROUND_GC) && defined(FEATURE_REDHAWK)
831-
832-
// Helper used to wrap the start routine of background GC threads so we can do things like initialize the
833-
// Redhawk thread state which requires running in the new thread's context.
834-
uint32_t WINAPI gc_heap::rh_bgc_thread_stub(void * pContext)
835-
{
836-
rh_bgc_thread_ctx * pStartContext = (rh_bgc_thread_ctx*)pContext;
837-
838-
// Initialize the Thread for this thread. The false being passed indicates that the thread store lock
839-
// should not be acquired as part of this operation. This is necessary because this thread is created in
840-
// the context of a garbage collection and the lock is already held by the GC.
841-
ASSERT(GCHeap::GetGCHeap()->IsGCInProgress());
842-
GCToEEInterface::AttachCurrentThread();
843-
844-
// Inform the GC which Thread* we are.
845-
pStartContext->m_pRealContext->bgc_thread = GetThread();
846-
847-
// Run the real start procedure and capture its return code on exit.
848-
return pStartContext->m_pRealStartRoutine(pStartContext->m_pRealContext);
849-
}
850-
851-
#endif // BACKGROUND_GC && FEATURE_REDHAWK
852-
853830
#ifdef FEATURE_BASICFREEZE
854831
segment_handle GCHeap::RegisterFrozenSegment(segment_info *pseginfo)
855832
{

src/gc/gcpriv.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,19 +2736,6 @@ class gc_heap
27362736
static
27372737
uint32_t __stdcall bgc_thread_stub (void* arg);
27382738

2739-
#ifdef FEATURE_REDHAWK
2740-
// Helper used to wrap the start routine of background GC threads so we can do things like initialize the
2741-
// Redhawk thread state which requires running in the new thread's context.
2742-
static uint32_t WINAPI rh_bgc_thread_stub(void * pContext);
2743-
2744-
// Context passed to the above.
2745-
struct rh_bgc_thread_ctx
2746-
{
2747-
PTHREAD_START_ROUTINE m_pRealStartRoutine;
2748-
gc_heap * m_pRealContext;
2749-
};
2750-
#endif //FEATURE_REDHAWK
2751-
27522739
#endif //BACKGROUND_GC
27532740

27542741
public:

src/gc/sample/gcenv.ee.cpp

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,6 @@ bool GCToEEInterface::CatchAtSafePoint(Thread * pThread)
191191
return pThread->CatchAtSafePoint();
192192
}
193193

194-
// does not acquire thread store lock
195-
void GCToEEInterface::AttachCurrentThread()
196-
{
197-
ThreadStore::AttachCurrentThread();
198-
}
199-
200194
void GCToEEInterface::GcEnumAllocContexts (enum_alloc_context_func* fn, void* param)
201195
{
202196
Thread * pThread = NULL;
@@ -218,6 +212,12 @@ void GCToEEInterface::SyncBlockCachePromotionsGranted(int /*max_gen*/)
218212
{
219213
}
220214

215+
bool GCToEEInterface::CreateBackgroundThread(Thread** thread, GCBackgroundThreadFunction threadStart, void* arg)
216+
{
217+
// TODO: Implement for background GC
218+
return false;
219+
}
220+
221221
void FinalizerThread::EnableFinalization()
222222
{
223223
// Signal to finalizer thread that there are objects to finalize
@@ -229,12 +229,6 @@ bool FinalizerThread::HaveExtraWorkForFinalizer()
229229
return false;
230230
}
231231

232-
bool REDHAWK_PALAPI PalStartBackgroundGCThread(BackgroundCallback callback, void* pCallbackContext)
233-
{
234-
// TODO: Implement for background GC
235-
return false;
236-
}
237-
238232
bool IsGCSpecialThread()
239233
{
240234
// TODO: Implement for background GC

src/vm/assembly.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,8 +2343,6 @@ static void Stress_Thread_Start (LPVOID lpParameter)
23432343
for (n = 0; n < dwThreads-1; n ++)
23442344
{
23452345
threads[n] = SetupUnstartedThread();
2346-
if (threads[n] == NULL)
2347-
COMPlusThrowOM();
23482346

23492347
threads[n]->m_stressThreadCount = dwThreads/2;
23502348
Stress_Thread_Param *param = ((Stress_Thread_Param*)lpParameter)->Clone();

src/vm/ceemain.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3727,8 +3727,7 @@ void InitializeGarbageCollector()
37273727
IfFailThrow(hr);
37283728

37293729
// Thread for running finalizers...
3730-
if (FinalizerThread::FinalizerThreadCreate() != 1)
3731-
ThrowOutOfMemory();
3730+
FinalizerThread::FinalizerThreadCreate();
37323731

37333732
// Now we really have fully initialized the garbage collector
37343733
SetGarbageCollectorFullyInitialized();

src/vm/finalizerthread.cpp

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -994,13 +994,13 @@ DWORD __stdcall FinalizerThread::FinalizerThreadStart(void *args)
994994
return 0;
995995
}
996996

997-
DWORD FinalizerThread::FinalizerThreadCreate()
997+
void FinalizerThread::FinalizerThreadCreate()
998998
{
999-
DWORD dwRet = 0;
1000-
1001-
// TODO: The following line should be removed after contract violation is fixed.
1002-
// See bug 27409
1003-
SCAN_IGNORE_THROW;
999+
CONTRACTL{
1000+
THROWS;
1001+
GC_TRIGGERS;
1002+
MODE_ANY;
1003+
} CONTRACTL_END;
10041004

10051005
#ifndef FEATURE_PAL
10061006
if (!CLRMemoryHosted())
@@ -1021,17 +1021,14 @@ DWORD FinalizerThread::FinalizerThreadCreate()
10211021

10221022
_ASSERTE(g_pFinalizerThread == 0);
10231023
g_pFinalizerThread = SetupUnstartedThread();
1024-
if (g_pFinalizerThread == 0) {
1025-
return 0;
1026-
}
10271024

10281025
// We don't want the thread block disappearing under us -- even if the
10291026
// actual thread terminates.
10301027
GetFinalizerThread()->IncExternalCount();
10311028

10321029
if (GetFinalizerThread()->CreateNewThread(0, &FinalizerThreadStart, NULL))
10331030
{
1034-
dwRet = GetFinalizerThread()->StartThread();
1031+
DWORD dwRet = GetFinalizerThread()->StartThread();
10351032

10361033
// When running under a user mode native debugger there is a race
10371034
// between the moment we've created the thread (in CreateNewThread) and
@@ -1046,23 +1043,17 @@ DWORD FinalizerThread::FinalizerThreadCreate()
10461043
// debugger may have been detached between the time it got the notification
10471044
// and the moment we execute the test below.
10481045
_ASSERTE(dwRet == 1 || dwRet == 2);
1049-
if (dwRet == 2)
1050-
{
1051-
dwRet = 1;
1052-
}
10531046

10541047
// workaround wwl: make sure finalizer is ready. This avoids OOM problem on finalizer
10551048
// thread startup.
10561049
if (CLRTaskHosted()) {
10571050
FinalizerThreadWait(INFINITE);
10581051
if (!s_FinalizerThreadOK)
10591052
{
1060-
dwRet = 0;
1053+
ThrowOutOfMemory();
10611054
}
10621055
}
10631056
}
1064-
1065-
return dwRet;
10661057
}
10671058

10681059
void FinalizerThread::SignalFinalizationDone(BOOL fFinalizer)

src/vm/finalizerthread.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class FinalizerThread
9191
static void FinalizeObjectsOnShutdown(LPVOID args);
9292
static DWORD __stdcall FinalizerThreadStart(void *args);
9393

94-
static DWORD FinalizerThreadCreate();
94+
static void FinalizerThreadCreate();
9595
static BOOL FinalizerThreadWatchDog();
9696
};
9797

src/vm/gcenv.ee.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,39 @@ void GCToEEInterface::DisablePreemptiveGC(Thread * pThread)
746746
WRAPPER_NO_CONTRACT;
747747
pThread->DisablePreemptiveGC();
748748
}
749+
750+
bool GCToEEInterface::CreateBackgroundThread(Thread** thread, GCBackgroundThreadFunction threadStart, void* arg)
751+
{
752+
CONTRACTL
753+
{
754+
NOTHROW;
755+
GC_TRIGGERS;
756+
}
757+
CONTRACTL_END;
758+
759+
Thread* newThread = NULL;
760+
EX_TRY
761+
{
762+
newThread = SetupUnstartedThread(FALSE);
763+
*thread = newThread;
764+
}
765+
EX_CATCH
766+
{
767+
}
768+
EX_END_CATCH(SwallowAllExceptions);
769+
770+
if ((newThread != NULL) && newThread->CreateNewThread(0, (LPTHREAD_START_ROUTINE)threadStart, arg))
771+
{
772+
newThread->SetBackground (TRUE, FALSE);
773+
774+
// wait for the thread to be in its main loop, this is to detect the situation
775+
// where someone triggers a GC during dll loading where the loader lock is
776+
// held.
777+
newThread->StartThread();
778+
779+
return true;
780+
}
781+
782+
*thread = NULL;
783+
return false;
784+
}

0 commit comments

Comments
 (0)