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.

Comments (1)

  1. Matt Garven says:

    Eric, a slight correction to my comment in your previous post – I think the local ConsoleCtrl in your Main method is eligible for GC when a debugger is not attached (I had previously said "in release mode" which is not the case).

    We’ve seen this behaviour in our unit tests in particular cirumstances.

    I think before you say "everything is fine", you should add this line after your Thread.Sleep line:

    GC.KeepAlive(consoleCtrl);