Why does my thread handle suddenly go bad? All I did was wait on it!


A customer reported that they had a very strange bug, where waiting on a thread handle causes it to become invalid. Here's a code fragment:

DWORD waitResult = WaitForSingleObject(hThread, INFINITE);
assert(waitResult == WAIT_OBJECT_0); // assertion passes

DoSomeCleanup();

CloseHandle(hThread);

That final call to Close­Handle results in a STATUS_INVALID_HANDLE exception when run in the debugger. How did the handle become invalid? We sucessfully waited on it just a few lines earlier.

There wasn't enough information to go on, so we had to make some guesses. Perhaps hThread was already closed, and it got recycled to refer to some unrelated kernel object, and it's that unrelated object that you're waiting on when you call Wait­For­Single­Object. And then when you do some cleanup, that causes the unrelated handle to be closed, which means that the numeric value of hThread now refers to an invalid handle.

The customer did some investigation and discovered that they were obtaining the thread handle from the _begin­thread function. The handle returned by the _begin­thread function is explicitly documented as being closed by the _end­thread function.

_end­thread automatically closes the thread handle, whereas _end­thread­ex does not. Therefore, when you use _begin­thread and _end­thread, do not explicitly close the thread handle by calling the Win32 Close­Handle API. This behavior differs from the Win32 Exit­Thread API.

Basically, the deal is that the _begin­thread function returns a handle to the created thread, but does not give you ownership of the handle. Ownership of that handle remains with the thread itself, and the thread automatically closes the handle when it exits. (Not mentioned in the MSDN documentation for _begin­thread is that the runtime automatically calls _end­thread if the thread function returns normally. This detail is mentioned in the documentation for _end­thread, which is probably a better place for it anyway.)

Basically, the handle returned by the _begin­thread function is useless. You don't know how long it's valid, and it might even be invalid by the time you even receive it!

Switching to _end­thread­ex fixed the problem.

Comments (14)
  1. Medinoc says:

    What happens when one simply returns from the thread callback? I’d suspect the code gluing between _beginthread() and its callback calls _endthread() upon return, while the code between _beginthreadex() and its callback calls _endthreadex() instead?

  2. Joshua says:

    A hollow voice whispered CreateThread().

    1. Eric says:

      CreateThread is only useful if you’re not using the C Runtime Library. _beginthread() initializes the CRT.

      1. Cesar says:

        > _beginthread() initializes the CRT.

        Which CRT? A process can have more than one CRT, and the thread function can even call functions from several different C runtimes. If CreateThread() is broken, then _beginthread() is broken too.

        1. Joshua says:

          I’m in the process of reducing out MS’s CRT. It’s pretty much useless for Unicode aware applications. wsnprintfW is in one of the Windows DLLs and the dozen platform functions are quite writable in C or asm.

      2. Harry Johnston says:

        The CRT has been self-initializing for, what, more than a decade now? As far as I know, the only remaining advantage to _beginthread[ex] is that it supposedly handles running out of memory better, but in practice very few applications will survive running out of memory anyway.

        1. Darran Rowe says:

          When the UCRT refactor happened, I dug through the source for fun. Yes, I know.
          Anyway, if a thread tries to access per thread data but TlsGetValue/FlsGetValue returns null, then it will initialise the per thread data there.
          If the Fls functions are available then it will prefer those because they have the advantage of cleaning up the per thread data without effort. On Windows XP, creating a thread with CreateThread could leak memory.
          Well anyway, while this is all implementation details, it is still interesting.

          1. Harry Johnston says:

            Oh, yes, the memory leaks are another edge case, I forgot about that. But keep in mind that they’re small, and only occur when using a static runtime, and only on obsolete versions of Windows.

  3. AsmGuru62 says:

    “runtime automatically calls _end­thread if the thread function returns normally”
    Does this statement holds true for some older compiler?
    Like VC++ 6.0?
    Just curious.
    I see _endthread all over my code (which I inherited).
    Threads just call it before returning.

    1. Buster says:

      AsmGuru62, no thread calls _endthread before returning, or in other words, no thread returns after calling _endthread. Indeed, a thread does nothing at all after calling _endthread, because _endthread ends the thread.

    2. Darran Rowe says:

      If the thread calls _beginthread then yes it should still hold true.
      It is documented in the documentation for _endthread, even in the VC6 version.
      https://msdn.microsoft.com/en-us/library/aa246693(v=vs.60).aspx
      It has a chance to do this because the thread routine that is passed into _beginthread uses the __cdecl calling convention where CreateThread uses __stdcall. So there needs to be a CRT thread start routine to call into yours. Your thread would then return to this and the call to _endthread should be in there.

  4. Joshua Schaeffer says:

    Instead of automatically closing the handle, how about automatically never opening the handle?

  5. How does the C runtime interop with bare Win32 threads or other runtimes?

    1. Joshua says:

      Badly.

Comments are closed.

Skip to main content