Thread.Abort is a Sign of a Poorly Designed Program

Published on Wednesday, August 22, 2007

Source

Thread.Abort is a Sign of a Poorly Designed Program

Continuing the theme of Thead.Sleep is a sign of a poorly designed program, I've been meaning to provide similar detail on Thread.Abort and not just allude to it in other posts like 'System.Threading.Thread.Suspend() is obsolete: 'Thread.Suspend has been deprecated….

Many of the concepts I've discussed regarding Thread.Suspend also apply to Thread.Abort, and in much the same way that the ability to terminate a thread has existed for so long that the concept has remained ubiquitous when dealing with threads and it just keeps getting implemented without thought. Thread.Abort is far more unsafe than Thread.Suspend; but, unfortunately Thread.Abort has yet to be deprecated.

Unlike Thread.Suspend, Thread.Abort stops your thread and it can't continue afterwards. In the same vein as Thread.Suspend, Thread.Abort doesn't know anything about your thread, and ifyour thread isn't in a try blockthat handles ThreadAbortException, it just stops it where it is, which may be in the middle of updating a complex invariant. This means the threadcan't continue where the left off like Thread.Suspend and Thread.Resume and can never get that invariant out of that corrupt state (and nothing else can, it has no idea where that thread left off).

Thread.Abort isn't a full-blown terminate; it does cause all finally blocks that it knows about to execute before your thread is stopped and won't terminate your thread while it's in a catch or finally block.

There's several concepts that go along with Thread.Abort. One I've already mentioned, the ThreadAbortException exception. Catching ThreadAbortException is conceptually cooperative thread termination. My issue with catching ThreadAbortException is that you're essentially using exceptions for normal flow of logic; which I don't agree with. The other concept is critical regions. You might think that wrapping critical code with Thread.BeginCritcalRegion and Thread.EndCriticalRegion will mean your thread won't be aborted while it's executing that code. Unfortunately, the .NET Framework doesn't really us Begin/EndCriticalRegion for anything. But, that's also not the intention, the intention is to signify to the runtime (currently only SQL Server vNext, I believe, uses that information) that should a Thread.Abort be called on that thread, or an unhandled exception occur, corruption has occurred and it should simply unload the thread'sAppDomain.

But, the concept of interrupting the thread in the middle of something is really why you should never use Thread.Abort. It's one thing to interrupt you own code, but Thread.Abort actually as the potential to interrupt lock statements and cause locks not to be unlocked. As Joe Duffy points out, there's a check when running code on the x86 that makes sure Thread.Abort can't interrupt between a call to Monitor.Enter when it is adjacent to a protected region. But, on current 64-bit JITs this check does not exist and a thread can be aborted between the call to Monitor.Enter and the start of the protected region.

Unfortunately, the x86 is not entirely immune. In the C# compiler, when optimizations are turned off, it emits no-ops in various places. I can't really confirm why, but the prevalent theory is to allow the Visual Debugger to put breakpoints on source code that otherwise wouldn't have corresponding IL. (like the start brace, for example). Unfortunately there's a bug in the generation of that IL that means Thread.Abort can cause locks not to be unreleased.

Eric Lippert recently provided some of the detail of the bug on his blog. But, essentially, this is what the C# compiler generates for IL code for the lock statement:
call void [mscorlib]System.Threading.MonitorEnter(object)
Hole:nop
StartTry:nop
ldstr "in lock"
call void [mscorlib]System.Console
WriteLine(string)
nop
nop
leave.s EndFinally
EndTry:ldloc.0
call void [mscorlib]System.Threading.Monitor::Exit(object)
nop
endfinally
EndFinally:nop
ret
.try StartTry to EntTry finally handler EndTry to EndFinally

Which is emitted(from both .NET 2.0 and .NET 3.5 Beta 2) for the compilationof the following:

lock (locker)

{

Console.WriteLine("in lock");

}

This means the lock is acquired (call to Monitor.Enter) and a nop (at Hole) is executed before the start of the protected region (try block). This means there's an instruction after the acquisition of the lock that isn't in the try block. It's at that point if Thread.Abort were called, the finally block would never get executed and the lock would not be released. Likely this would result in deadlocks because nothing can ever release that lock now.

If you never use Thread.Abort, this issue doesn't affect you. But, it really just provides another good reason why people keep saying you should never use Thread.Abort.

In 'System.Threading.Thread.Suspend() is obsolete: 'Thread.Suspend has been deprecated…. I provide an example of cooperatively terminating a thread, if you're interested in terminating your thread without using Thread.Abort.

Like catching Exception, there is an instance when Thread.Abort can be safely used: when you're terminating your application. If you're terminating your application and you know you have a foreground thread running and it's not responding, Thread.Abort can safely be used to ensure your application terminates bcause you're shutting down and not using any existing invariants or locks.

comments powered by Disqus