Func-eval abort is evil
Func-eval is evil. Func-eval abort is even worse. For those coming in late, Func-eval is when the debugger hijacks a thread and has it evaluate some function such as a property-getter or to-string. Func-eval abort is when that evaluation hangs, and then the debugger aborts it (similar to Thread.Abort).
Visual Studio sets up a timer when it does automatic func-evals, and will automatically issue an abort after a few seconds.
For example, say you have a very very evil property like the one below.
class Evil
{
public string EvilProperty
{
get
{
Console.WriteLine("start");
Thread.Sleep(1000 * 10);
Console.WriteLine("done");
return "finished!";
}
}
}
If you allocate an Evil object in VS, and then view it in the watch window, VS will automatically func-eval the property getters (if func-eval is enabled). If the eval takes too long (which that Sleep will ensure in this case), it will abort the func-eval. It looks like this:
(screenshots courtesy of Windows Live Writer! )
ICorDebug?
For debugger authors implementing func-eval support, the corresponding APIs are ICorDebugEval::Abort and ICorDebugEval2::RudeAbort. Like most ICorDebug operations, these are asynchronous. Issue them, call Continue(), and then wait for an Abort Complete (EvalComplete/EvalException) event.
So why is it evil?
Some reasons why abort is evil and should not be a backbone of any debugging strategy:
- Abort may not always be possible. For example, the CLR can't abort a thread in native code. This can be a very common scenario for winforms properties that do a SendMessage.
- Abort has all the same problems of Thread.Abort and more. For example, it can leave the managed user state in an undefined position. For example, if you abort a thread in the middle of updating some user data structure (eg,a hash), that structure may then be left in an inconsistent state.
- At a abstract level, we all recognize that func-evals change program behavior; and aborts are even worse. Func-eval at least executes an entire function atomically. Abort executes a random subset of the function, and can thus have very significant unpredictable behavior changing affects.
- Rude-abort doesn’t run backout code, and may leave the thread in an undefined state. For example, it may leave locks orphaned causing strange deadlocks later on.
- If abort becomes very common, these previous issues may lead to a lot of “My code breaks under the debugger, it’s a debugger bug”.
- Use features the way they were intended. Abort was supposed to be a mitigation for stray func-evals, not a mainline scenario.