What's wrong with this code #7 - Discussion

In this edition, I presented a small chunk of code that attempted to hook the signal handlers, so that we could do something smart when the user hits CTRL-C, rather than just rolling over and dying (rolling over optional).

But before I discuss the answers, I'd like to talk about the history of this code. This is one that I wrote, and sent out as an answer to a question. An hour or so later I got a reply from Anders who said, "that won't work because..."

Well, because of the reason that I'm going to talk about in a little while. But first, the things I wasn't looking for, in a pathetic attempt to generate some sense of expectation, which is really a futile exercise because of what people write in the comments. Maybe I should do one of these where I don't publish the comments until I get to this point.

Code exits after 10 CTRL_BREAKS

Um... No. I really should have stated that the problem was not in the Program class

Logger method doesn't return Bool

This is a fair criticism, as it might effect whether other handlers get called, but that's probably a fairly rare scenario.

Should synchronize the access to count because it's static
Application will keep running after count > 10

Possibly true, assuming that this method can be called on multiple threads at the same time. Not covered in the MSDN docs, and I'm too lazy to look at the source to see what it does right now. Irrelevant to the main point.

100000 seconds != 27 hours

Okay 27/1000 of an hour

A Hint

If you compiled this code and ran it, it would very probably run correctly for you. But if you changed Main() to be:

 public static void Main()
{
ConsoleCtrl consoleCtrl = new ConsoleCtrl();

  consoleCtrl.ControlEvent += new ConsoleCtrl.ControlEventHandler(Logger);
GC.Collect();
GC.WaitForPendingFinalizers();

  Thread.Sleep(100000);
}

You would find the problem.

It has to do with the way that the interop layer deals with delegates that are passed to it. Callbacks are used in different ways in unmanaged code, so the only thing that the interop layer can be sure of is that the callback will be used during the execution of the call, and it makes sure that there is a reference to the delegate during that time.

But when you return, that's no longer the case, even though SetConsoleCtrlHandler() still needs the delegate, so if the GC collects it, you're in trouble.

The fix is simple - declare an instance field of type ControlEventHandler, and store the delegate in that instance instead of creating the instance as part of the call to SetConsoleCtrlHandler(). Then everything is fine.