October 25th, 2013

My, those threads start up really fast nowadays

Here’s a little puzzle inspired by an actual bug:

// global variable
DWORD g_WorkerThreadId;
bool IsRunningOnWorkerThread()
{
  return GetCurrentThreadId() == g_WorkerThreadId;
}
bool LaunchWorkerThread()
{
 HANDLE hThread = CreateThread(nullptr, 0,
                               WorkerThread,
                               nullptr, 0,
                               &g_WorkerThreadId);
 if (hThread != nullptr) {
   CloseHandle(hThread);
   return true;
 }
 return false;
}
DWORD CALLBACK WorkerThread(void *Proc)
{
  // Can this assertion ever fire?
  assert(IsRunningOnWorkerThread());
  return 0;
}

Can the assertion at the start of WorkerThread ever fire?

Naturally, the answer is Yes, otherwise it wouldn’t be a very interesting article.

The assertion can fire if the worker thread starts running before the call the Create­Thread returns. In that case, the caller hasn’t yet received the handle or ID of the newly-started thread. The new thread calls Is­Running­On­Worker­Thread, which returns false since g_Worker­Thread­Id hasn’t been initialized yet.

The actual bug was something along the lines of this:

void DoSomething()
{
  if (IsRunningOnWorkerThread()) {
     .. do it one way ..
  } else {
     .. do it the other way ..
  }
}
void DoManyThings()
{
  DoSomething();
  DoSomethingElse();
  DoYetAnotherThing();
}
DWORD CALLBACK WorkerThread(void *Proc)
{
  ...
  DoManyThings();
  ...
  return 0;
}

If the new thread started up so quickly that the original thread doesn’t get a chance to receive the new thread ID and put it into g_Worker­Thread­ID, then the Do­Something function called from the worker thread will accidentally do things the not-on-the-worker-thread way, and then things start go go awry.

One way to address is is to add suspenders to your belt:

DWORD CALLBACK WorkerThread(void *Proc)
{
  g_WorkerThreadId = GetCurrentThreadId();
  ...

By having both the original thread and the created thread set the g_WorkerThreadId variable, you cover both cases of the race. If the original thread runs faster, then the CreateThread function will set the g_WorkerThreadId variable to the ID of the worker thread, and the first line of Worker­Thread will be redundant. On the other hand, if the worker thread runs faster, then the assignment at the beginning of Worker­Thread sets the thread ID, and the assignment performed by the CreateThread function will be redundant.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.