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

[Local GC] Unify background GC thread and server GC thread creation #14821

Merged
merged 15 commits into from Nov 9, 2017

Conversation

swgillespie
Copy link

Apologies for the big PR - this ended up being a nontrivial refactoring that resulted in a fair amount of code shuffle.

The big idea behind this PR is to unify the creation of threads in the GC so that they all go through the same code path. The GC creates threads for two reasons:

  1. Background GC threads. One thread is created for every heap but they are left unaffinitized and run at the default scheduling priority. They are unique in that they can be suspended and, as such, have a cooperative/preemptive mode; if a foreground GC happens during a background GC, the GC will transition the running BGC thread to preemptive mode and back (blocking the BGC thread until the FGC completes).
  2. Server GC threads. One thread is created for every heap and they (by default) are affinitized to a single core. They run at a higher priority than the default and can't be suspended.

Previously, these two threads were created with two different APIs: background GC threads were created with GCToEEInterface::CreateBackgroundThread while server GC threads were created with GCToOSInterface::CreateThread. This PR unifies them to instead both use GCToEEInterface::CreateThread, a new API.

In order to accomodate these two thread types, GCToEEInterface::CreateThread has two code paths; one for threads that can be suspended and ones that can't. They are adapted from the existing code in both GCToEEInterface::CreateBackgroundThread and GCToOSInterface::CreateThread, respectively. Some additional thread setup code that previously lived in gc.cpp (e.g. committing the thread stack and setting ThreadType_GC on server GC thrads) was also moved to GCToEEInterface::CreateThread, where it should be.

Since GCToEEInterface::CreateThread doesn't set thread affinity or thread priority, two new GCToOSInterface APIs were added to do so: GCToOSInterface::SetThreadAffinity and GCToOSInterface::BoostThreadPriority. These have been implemented for the non-standalone GC case and in the standalone GC case for Windows platforms:

0:014> ~11   <-- server GC thread
  11  Id: 5394.779c Suspend: 1 Teb: 00000057`68673000 Unfrozen
      Start: CoreCLR!<lambda_5c590d503e0569183472d6ea9c775c63>::<lambda_invoker_cdecl> (00007ff9`c5813900)
      Priority: 2  Priority class: 32  Affinity: 40 <-- boosted priority and single proc affinity
0:014> ~0    <-- user thread
   0  Id: 5394.3ae4 Suspend: 1 Teb: 00000057`6865d000 Unfrozen
      Start: CoreRun!wmainCRTStartup (00007ff6`8a6e3aa0)
      Priority: 0  Priority class: 32  Affinity: ff
0:014> ~10  <-- server GC thread
  10  Id: 5394.5d40 Suspend: 1 Teb: 00000057`68671000 Unfrozen
      Start: CoreCLR!<lambda_5c590d503e0569183472d6ea9c775c63>::<lambda_invoker_cdecl> (00007ff9`c5813900)
      Priority: 2  Priority class: 32  Affinity: 20 <-- boosted priority and single proc affinity

@jkotas @janvorli @sergiy-k PTAL? also cc @Maoni0 who is OOF

@@ -82,6 +79,7 @@ class GCToEEInterface
static void FreeStringConfigValue(const char* key);
static bool IsGCThread();
static bool IsGCSpecialThread();
static bool CreateThread(void (*threadStart)(void*), void* arg, bool is_special, const wchar_t* name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to avoid naming things special wherever possible. special is very cryptic.

What about calling this flag suspendable?

(Also, it would be nice to rename IsGCSpecialThread to something more self-descriptive.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do!


bool GCToEEInterface::CreateThread(void (*threadStart)(void*), void* arg, bool is_special, const wchar_t* name)
{
return nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method returns bool...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, you're right - this is left over from a previous refactor.

@@ -60,6 +59,7 @@ class GCToEEInterface : public IGCToCLR {
void FreeStringConfigValue(const char* value);
bool IsGCThread();
bool IsGCSpecialThread();
bool CreateThread(void (*threadStart)(void*), void* arg, bool is_special, const wchar_t* name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Indentation

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my merge tool inserts tabs... 😭 . will fix.

@swgillespie
Copy link
Author

Looks like I have wide-char issues... I'll fix that.

I'm also thinking of naming IsGCSpecial to CurrentThreadCreatedByGC as it more clearly describes what it does, if there aren't any objections (and per Jan's review comment).


WCHAR threadName[MaxThreadNameSize];
LPWSTR wideName = nullptr;
if (MultiByteToWideChar(CP_ACP, 0, name, -1 /* null terminated */, threadName, MaxThreadNameSize))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use UTF8 here. Or even better - use SString that will do this conversion for you.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do! was not aware that SString existed.

{
#ifdef BACKGROUND_GC
// For background GC we revert to doing a blocking GC.
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It the BACKGROUND_GC defined when compiling VM?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also, CommitThreadStack is no-op in CoreCLR. You can just delete the whole block.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not - I've been looking around and it looks like this is supposed to be defined by gcpriv.h, so it won't have any effect in VM code.

Evidence that it does nothing is this block of code: https://github.com/dotnet/coreclr/blob/master/src/vm/finalizerthread.cpp#L1121-L1126, which definitely would not compile if BACKGROUND_GC were defined...

at any rate, I'll just delete the block.


if (!args.Thread->CreateNewThread(0, threadStub, &args, wideName))
{
args.Thread->DecExternalCount(FALSE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close ThreadStartedEvent ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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 CurrentThreadWasCreatedByGC();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - it seems it would be nice to start this function name with the verb (WasCurrentThreadCreatedByGC)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok!

@swgillespie
Copy link
Author

could be a server GC deadlock on startup... re-running to see if it repros. I'm currently trying to repro on a mac right now.

@dotnet-bot test Ubuntu x64 Checked Innerloop Build and Test

…restarting a non-suspended thread is incorrect
return true;
}

bool CreateUnsuspendableThread(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: CreateNonSuspendableThread may look better

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok!

@swgillespie
Copy link
Author

I'm not sure if this is expected behavior in the PAL's implementation of ResumeThread, but it looks like calling ResumeThread on a thread that is not suspended has the potential to deadlock on non-Windows platforms. The Ubuntu Server GC CI failures are due to a failure to acquire the suspension lock for the suspending thread, which deadlocks. It wasn't clear to me who was holding the suspension lock and I didn't look any further since it was incorrect that my code was attempting to resume a non-suspended thread anyway.

@swgillespie
Copy link
Author

@dotnet-bot test Ubuntu16.04 armlb Cross Debug Innerloop Build
@dotnet-bot test Tizen armel Cross Checked Innerloop Build and Test

@swgillespie
Copy link
Author

@dotnet-bot test Tizen armel Cross Checked Innerloop Build and Test

@swgillespie
Copy link
Author

Not really sure what "Perf Build and Test" is, but I believe I've addressed all outstanding code review comments and passed the CI so I think this should be ready to merge if there aren't any objections.

@swgillespie swgillespie merged commit e64cbb5 into dotnet:master Nov 9, 2017
@swgillespie
Copy link
Author

thanks for the reviews!

@swgillespie swgillespie deleted the gc-create-thread branch November 9, 2017 17:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
4 participants