Windows started picking up the really big pieces of TerminateThread garbage on the sidewalk, but it's still garbage on the sidewalk


Ah, Terminate­Thread. There are still people who think that there are valid scenarios for calling Terminate­Thread.

Can you explain how Exit­Thread works?

We are interested because we have a class called Thread­Class. We call the Start() method , and then the Stop() method, and then the Wait­Until­Stopped() method, and then the process hangs with this call stack:

ntdll!ZwWaitForSingleObject
ntdll!RtlpWaitOnCriticalSection
ntdll!RtlEnterCriticalSection
ntdll!LdrShutdownThread
ntdll!RtlExitUserThread
kernel32!BaseThreadInitThunk
ntdll!RtlUserThreadStart

Can you help us figure out what's going on?

From the stack trace, it is clear that the thread is shutting down, and the loader (Ldr) is waiting on a critical section. The critical section the loader is most famous for needing is the so-called loader lock which is used for various things, most notably to make sure that all DLL thread notification are serialized.

I guessed that the call to Wait­Until­Stopped() was happening inside Dll­Main, which created a deadlock because the thread cannot exit until it delivers its Dll­Main notifications, but it can't do that until the calling thread exits Dll­Main.

The customer did some more debugging:

The debugger reports the critical section as

CritSec ntdll!LdrpLoaderLock+0 at 77724300
WaiterWoken        No
LockCount          3
RecursionCount     1
OwningThread       a80
EntryCount         0
ContentionCount    3
*** Locked

The critical section claims that it is owned by thread 0xa80, but there is no such active thread in the process. In the kernel debugger, a search for that thread says

Looking for thread Cid = a80 ...
THREAD 8579e1c0  Cid 0b58.0a80  Teb: 00000000 Win32Thread: 00000000 TERMINATED
Not impersonating
DeviceMap                 862f8a98
Owning Process            0       Image:         <Unknown>
Attached Process          84386d90       Image:         Contoso.exe
Wait Start TickCount      12938474       Ticks: 114780 (0:00:29:50.579)
Context Switch Count      8             
UserTime                  00:00:00.000
KernelTime                00:00:00.000
Win32 Start Address 0x011167c0
Stack Init 0 Current bae35be0 Base bae36000 Limit bae33000 Call 0
Priority 10 BasePriority 8 PriorityDecrement 2 IoPriority 2 PagePriority 5

Contoso.exe is our process.

Okay, we're getting somewhere now. The thread 0xa80 terminated while it held the loader lock. When you run the program under a debugger, do you see any exceptions that might suggest that the thread terminated abnormally?

We found the cause of the problem. We use Terminate­Thread in the other place. That causes the thread to continue to hold the loader lock after it has terminated.

It's not clear what the customer meant by "the other place", but no matter. The cause of the problem was found: They were using Terminate­Thread.

At this point, Larry Osterman was inspired to write a poem.

How many times does
it have to be said: Never
call TerminateThread.

In the ensuing discussion, somebody suggested,

One case where it is okay to use Terminate­Thread is if the thread was created suspended and has never been resumed. I believe it is perfectly legal to terminate it, at least in Windows Vista and later.

No, it is not "perfectly legal," for certain values of "perfectly legal."

What happened is that Windows Vista added some code to try to limit the impact of a bad idea. Specifically, it added code to free the thread's stack when the thread was terminated, so that each terminated thread didn't leak a megabyte of memory. In the parlance of earlier discussion, I referred to this as stop throwing garbage on the sidewalk.

In this case, it's like saying, "It's okay to run this red light because the city added a delayed green to the cross traffic." The city added a delayed green to the cross traffic because people were running the light and the city didn't want people to die. That doesn't mean that it's okay to run the light now.

Unfortunately, the guidance that says "Sometimes it's okay to call Terminate­Thread" has seeped into our own Best Practices documents. The Dynamic-Link Library Best Practices under Best Practices for Synchronization describes a synchronization model which actually involves calling Terminate­Thread.

Do not do this.

It's particularly sad because the downloadable version of the document references both Larry and me telling people to stop doing crazy things in Dll­Main, and terminating threads is definitely a crazy thing.

(The solution to the problem described in the whitepaper is not to use Terminate­Thread. It's to use the Free­Library­And­Exit­Thread pattern.)

Now the history.

Originally, there was no Terminate­Thread function. The original designers felt strongly that no such function should exist because there was no safe way to terminate a thread, and there's no point having a function that cannot be called safely. But people screamed that they needed the Terminate­Thread function, even though it wasn't safe, so the operating system designers caved and added the function because people demanded it. Of course, those people who insisted that they needed Terminate­Thread now regret having been given it.

It's one of those "Be careful what you wish for" things.

Comments (45)
  1. Joshua says:

    Yeah hmmm. I guess I always knew. I used to say back in the Windows XP days that it's one of those functions that must exist but must not be used.

    I used to think it was safe to call TerminateThread on a thread that had suspended itself (or in a spinlock)--I hadn't discovered EnterCriticalSection yet--but apparently not.

  2. AC says:

    Semi-OT: That's not a poem.

    And even if it fits the 5-7-5 pattern if you happen to put line breaks there, the pause in reading is definitely before "Never", not after.

  3. Andre says:

    I figure it's not just "those people who insisted that they needed Terminate­Thread now regret having been given it", it's also the Windows designers who regret giving in. They new it was horrible, now they have to implement work-arounds for it.

  4. Antonio &#39;Grijan&#39; says:

    One more for the collection of functions that must carry a big red warning saying "Only for use by debuggers" in its documentation. Any sane programmer should know s/he should keep away from TerminateThread. Any thread should be designed in a way that you can notify it that you want it to terminate, so it can do its clean up in a controlled fashion. If a thread does not respond to a request for termination, you should assume a worst case scenario (state or memory corruption), and take down the whole process as orderly as you can. Just another application of the "fail fast" principle.

  5. DC says:

    @AC: Looks like a poem to me. "Said" and "thread" rhyme, don't they?

  6. McBucket says:

    Larry Osterman's poem works as a haiku, I guess, but Western minds (like mine) will probably want a regular rhythmic meter scheme. I suggest:

    How many times does it have to be said,

    "Never, not ever, call TerminateThread."

  7. Cesar says:

    > because there was no safe way to terminate a thread

    I think there is one safe way to terminate a thread (so far I couldn't think of any other one): TerminateProcess. It can reliably terminate a thread without causing any of these issues, by ensuring that everything that could depend on the thread's state is cleaned up (by the kernel) at the same time.

  8. Muzer says:

    @McBucket I find it works better if you add dramatic pauses after "Never" and "Call":

    How many times does it have to be said?

    "Never[.] Call[.] TerminateThread."

  9. Douglas says:

    @Cesar

    >It can reliably terminate a thread […], by ensuring that everything […] is cleaned up […]

    Nevermind how DllMain doesn't get called on the loaded DLLs, what about mutexen (mutexes?). Don't they generate WAIT_ABANDONED?

  10. Myria says:

    TerminateThread should be considered a debugging-only API, like WriteProcessMemory and VirtualProtectEx.

  11. Mark VY says:

    @McBucket: Nice!  I like that one way more!

  12. Niklas says:

    If there were no TerminateThread function, how would Process Explorer implement its Kill Thread button?

  13. 12BitSlab says:

    If he used TerminateThread even a little bit,

    There is no way you can acquit.

  14. Joshua says:

    @Niklas: Debug it, set EIP = entry point of ExitThread.

  15. jader3rd says:

    <blockquote>The original designers felt strongly that no such function should exist because there was no safe way to terminate a thread, and there's no point having a function that cannot be called safely. But people screamed that they needed the Terminate­Thread function, even though it wasn't safe, so the operating system designers caved and added the function because people demanded it. Of course, those people who insisted that they needed Terminate­Thread now regret having been given it.</blockquote>

    Were there really customers who said that they wouldn't purchase Windows if there wasn't a TerminateThread function?

    [Developers who said that they wouldn't develop for Windows if there wasn't a TerminateThread function, so yeah. Developers are customers. -Raymond]
  16. Joshua Bowman says:

    The one and only good time I ever saw to use TerminateThread was for spyware that injected itself into csrss and other processes. Killing csrss would reboot the system, and I didn't have tools handy to fix it offline, but it turned out that the thread could be terminated and gave me just enough time to clean the infection before it re-injected itself (after a few false starts).

  17. Al Go says:

    "... call TerminateThread." - Larry Osterman in a poem.

  18. alegr1 says:

    "Never, not ever, call TerminateThread."

    Quoth the Raymond: "Nevermore."

  19. alegr1 says:

    [Developers who said that they wouldn't develop for Windows if there wasn't a TerminateThread function, so yeah]

    Those are like people who would not buy a car if there were no hole in the floor to brake with their feet.

  20. Neil says:

    What, never?

    No, never!

    What, never?

    Well, hardly ever.

  21. Killer{R} says:

    If TerminateThread() was designed as a kind of debug function - it should work normally only if target process is debugged by caller. Otherwise calling it should first bring-up fault report, then terminate thread and then - continue execution.

    All this would point that its a really API that has never be used during normal operation.

  22. Killer{R} says:

    BTW I have 'professional' USB sound interface card produced by well-known producers of sound cards that is known for everyone. As a 'pro' interface it has so-called 'ASIO' interface to work with sound. And everybody on forums complains that this interface ha very good hardware but so buggy drivers... I took debugger to see.. And you know what? Its user-mode part (dll that implements ASIO interface) uses TerminateThread() to stop its worker threads every time sound stream stopped.

    Thats why I use it only with Linux.

  23. Cesar says:

    @Killer{R}: That *looks* like a legitimate use of TerminateThread, since it knows the thread state (it's not holding any lock, just doing whatever computations it does), and this way can avoid checking a condition variable or something like that within a tight loop. But it won't properly release resources (by calling DllMain and so on). The "proper" way would be to suspend the thread, fiddle with its registers so it exits from the loop, and resume the thread; the code after the loop can then exit the thread cleanly.

    (Yes, I'm still joking, but slightly less than in the previous comment.)

  24. Killer{R} says:

    It definitely knows not everything cuz sometimes ('only' 1..2 times per day) app stuck or crashes. Not sure if this caused by TerminateThread() or some other bug though..

    Also its driver likes to BSOD if device disconnected while being used (why not? its USB!) so their Windows drivers pack is really terrible from bottom to top.. i.e. from userland to kernel mode. However they labeled its as 'beta' but never had 'release' drivers, while device came to market on 2006.

    Theoretically its looks possible to terminate suspended thread safely if it declared itself as 'terminatable' and then does only pure math without using any uncontrolled dependency like CRT, API, COM/RPC etc.. But that means that you must be 100% sure in your compiler or write that piece of code in assembler by own hands. And even then there'is some theoretical probability that some things like WOW64 will not secretly interfere with your execution.

    BTW there is good TerminateThread() and its called TerminateProcess().

  25. T. West says:

    I'll take a stab at defending the inclusion of TerminateThread.  Very early on in my career, I had a bit of code that needed a TerminateThread.  I read the warnings, but I could not find any other way of structuring the program to avoid it.  So I used it, and no surprise, all was fine.  After all, it *can* cause problems, but the vast majority of the time, you get away with it.  (Many years later, I actually looked at the code again, and now realized that the entire way I structured the program was obviously wrong, which is why I needed it in the first place, but heck, it's how programmers with no experience with multi-threaded programming think.

    And given that most of the code in the world is written by programmers at or below the median, a *lot* of programmers are going to require tools that make complicated jobs easier at the expense of not working at all some of the time.  It's not pretty, but sometimes you deliver for the customers you *have*, not the customer you'd like.

  26. Gabe says:

    I think ideally there would be a function that aborts a thread by injecting an exception into it, much like sending a signal in Unix. This would allow you to program defensively for it and make clean-up possible via stack unwinding.

    I believe this is how Thread.Abort in .NET works.

  27. Joshua says:

    @Gabe: If you've ever tried throwing through a Win32 callback function or SendMessage() you would know how scary that gets.

  28. Killer{R} says:

    2Gabe that would make TerminateThread unreliable (thread then may 'cancel' termination). That would break the main idea of TerminateThread.

  29. Anonymous Cow Herd says:

    TerminateThread is one of those things that seems like a perfectly logical and sensible idea - and so by deliberately not implementing it, developers feel like you're forcibly imposing your ideology on them (Thou Shalt Not Terminate Threads). "I know you [the scheduler] have the ability to terminate threads, so if I want to terminate a thread, why shouldn't I be able to?"

    But then when they actually try to use it, then they might find there's no way to make it work 100% of the time. (If 95% is good enough, then good for them, but obviously some developers want software to be reliable)

    I feel like the way to convince developers that no, they really don't want TerminateThread, is to challenge them to find a way to use it reliably, then explain why it won't work. Then repeat until they (or you) concede. Of course, that's impractically time consuming.

    By the way, gets and scanf are other functions that are very difficult to use reliably. (Impossible in the case of gets)

    Also I like this quote from T. West. above:

    > given that most of the code in the world is written by programmers at or below the median, a *lot* of programmers are going to require tools that make complicated jobs easier at the expense of not working at all some of the time.

  30. cheong00 says:

    It's like Response.End() on web - You should never need to use it(Just call <Current ApplicationInstance>.CompleteRequest() to suppress further event and return). If your web application is using it, you're doing something wrong.

  31. Sean Liming says:

    Aaaahhh a Vista left over. Vista-- good times :)

  32. Cesar says:

    @Anonymous Cow Herd: it *is* possible to use gets reliably. You just have to make sure that the standard input is a pipe only your program can write to, and only write to it less than the size of the buffer passed to gets.

    (Yes, it's a use case that'll never come up in real life. But it's a valid way to use gets while guaranteeing no buffer overflows.)

  33. acq says:

    There is still a significant number of open source libraries that frequently call TerminateThread.

    Do search through the sources of the libraries you use. Notify the authors (and be prepared to hear false claims that "it has to be done that way").

  34. Henke37 says:

    I hate to be the one to point it out, but the download has the same bad advice. Especially the line "* DLL A terminates T, knowing that it is in a consistent state.".

  35. Karellen says:

    @T.West: "given that most of the code in the world is written by programmers at or below the median"

    I'm going to have to contest this. Half of the programmers are at or below the median, by definition. For them to produce more code than the above-median half, the below-median programmers would have to be more productive than the above-median programmers, and I don't believe that that's the case.

    One could make the argument that below-median programmers produce large amounts of low-quality code, while above-median programmers produce smaller amounts of high-quality code, but I don't buy that. Certainly, below-median programmers do produce incredibly verbose low-quality code, but I don't think they produce much of it *per unit time* in the long run. While they manage to spew large quantities of copy-and-pasted mud initially, the bugginess of their secretions combined with their own lack of skill means they spend a long time chasing down corner cases, incorrectly-adapted pasted blocks, and other general bugs. Whereas above median programmers will produce cleaner code at a more steady pace, with shorter test/debug cycles, leading to more code generated overall.

  36. dirk gently says:

    @Douglas

    I would say "mutices" as in vortex->vortices or index->indices :)

  37. dave says:

    >For them to produce more code than the above-median half, the below-median programmers would have to be more productive than the above-median programmers, and I don't believe that that's the case.

    You are apparently confusing 'productivity' and 'lines of code'.

    It often seems to be the case that programmers who are less productive (in terms of complete and debugged function points) write more lines of code than I do.

    ;-)

  38. alegr1 says:

    One reason people resort to use of TerminateThread for DLL-created threads is that you cannot easily guarantee the safe thread shutdown for the DLL created thread, if you unload the DLL by FreeLibrary.

    One approach to solve that problem is to use a secondary DLL which the first DLL links explicitly (or by LoadLibrary/FreeLibrary). The thread code and all its possible callbacks should reside in the secondary DLL. DLL1 DllMain or some other Init function should call DLL2 function to start the worker thread. The worker thread needs to take a reference on DLL2 (LoadLibrary one more time). When DllMain1 is called with PROCESS_DETACH, it should ask the DLL2 to stop the worker thread (WITHOUT waiting for it). The thread should do FreeLibraryAndExitThread call. This will allow both DLLs to get cleanly unloaded.

  39. McBucket says:

    @dirk gently: "I would say "mutices" as in vortex->vortices or index->indices :)"

    Nice, but as the "-ex" stem is actually indicative of what the word "mutex" is short for (not the case for vortex, index, or vertex or codex, for that matter), that would be somewhat obfuscatory.

  40. Kevin says:

    @Karellen: "While they manage to spew large quantities of copy-and-pasted mud initially, the bugginess of their secretions combined with their own lack of skill means they spend a long time chasing down corner cases, incorrectly-adapted pasted blocks, and other general bugs."

    You misunderestimate the tendency of mediocre programmers to solve code problems with more code.  Never mind fixing the existing logic, we'll just slap on another layer to fix up the problems with the previous five layers.

  41. Karellen says:

    @Kevin: I don't think so, as I've been exposed to multitudes of unnecessary crufty layers before. I think I'm more likely to be overestimating how long it takes below-median programmers to a) find out approximately where another extra layer needs to be added, b) try adding a new layer in the wrong place, c) figure out why their "fix" isn't working, d) try adding a new layer in the "right" place (for suitably generous values of "right"), e) figure out what's wrong with their new layer and fix it so it actually works, and f) go back and fix all the existing users of the old layer that they just broke. Because in my experience, it's a _really_ long time. If you've got below-median programmers that can do all of the above with a quick turnaround time which enables them to put another layer in somewhere else, congratulations, yours must be closer to the median than mine were. ;-)

    I'm just glad I don't work there any more.

    [Who says that the below-median programmer bothers with steps (a), (c), or (f)? I also suspect most below-median programmers never think "I can solve this problem by deleting code / making existing code more flexible." The inner loop is "Add code. See if problem is fixed. If not, add more code. Repeat." -Raymond]
  42. Anonymous Cow Herd says:

    @Cesar: And there's probably a similar case for TerminateThread as well - a case that almost never actually happens where it's useful.

    Note that even in the self-pipe case, using gets buys you very little (it only saves you from removing the line terminator yourself).

  43. cheong00 says:

    @Karellen: The "above median" programmers got prompted to team lead or management positions and write less code (for the most time, they review others' code, not directly write it).

    Therefore I have no problem on that statement.

  44. Nico says:

    Bring out yer dead

    The scheduler said

    Don't want to Wait()

    For the same fate

    Just call TerminateThread

    Burma Shave

  45. Shinji Yamada says:

    McBucket: Larry Osterman's poem works as a haiku, I guess

    No it doesn't. The moraic structure is all wrong and the caesura is in the wrong place. And a proper haiku should be a deeply felt seasonal experience.

Comments are closed.

Skip to main content