Console.ReadKey .NET 4.5 changes may deadlock your system

September 12, 2012

2 comments

I’ve hit a weird issue today. We have a service that we run both as Windows service and from console. A specific use case seemed to cause our system to hang, but only when running from console. Also, I was sure this didn’t happen before I upgraded my machine to .NET 4.5. The service initialization code looks something like this:

serviceHost.Open();

 

while (Console.ReadKey().Key != ConsoleKey.Q)

{

    Console.WriteLine("Press Q to exit");

}

Basically, we start a WCF service, and wait for the user to press Q to exit the application. Fairly standard, and used to work until .NET 4.5.

Digging a little further, I found out that the system was stuck on some third-party code which was doing something like:

Console.Error.WriteLine("Error!");

We have other places that write to stdout (Console.WriteLine), but this seems to be the only place that writes to stderr (and only in very specific cases, which is why I didn’t notice it until today).

In order to reproduce the issue, I wrote the following program:

public static void Main()

{            

    var readTask = new Task(() =>

                                {

                                    Console.WriteLine("enter something");

                                    Console.ReadKey();

                                });

    var writeTask = new Task(() => Console.Error.WriteLine("hello"));

 

    readTask.Start();

    Thread.Sleep(100);            

    writeTask.Start();

 

    Task.WaitAll(readTask, writeTask);

}

We have two threads, one waits for an input from the user, and the other writes to Console.Error. This program will never terminate under .NET 4.5 (at least until you press a key), but will terminate under .NET 4.0. Fun! Let’s compare the .NET 4.5 and 4.0 source code in order to find out what happens here. Here is Console.ReadKey in .NET 4.5:

   1: Win32Native.InputRecord ir;

   2: int numEventsRead = -1; 

   3: bool r;

   4:  

   5: lock (InternalSyncObject) {

   6:    if (_cachedInputRecord.eventType == Win32Native.KEY_EVENT) { 

   7:        // We had a previous keystroke with repeated characters.

   8:        ir = _cachedInputRecord; 

   9:        if (_cachedInputRecord.keyEvent.repeatCount == 0) 

  10: ....

Can you see the lock in line 5? It doesn’t exist in the .NET 4.0 source. And here’s Console.Error in both .NET 4.0 and 4.5:

   1: public static TextWriter Error {

   2:     [HostProtection(UI=true)] 

   3:     get { 

   4:         Contract.Ensures(Contract.Result<TextWriter>() != null);

   5:         // Hopefully this is inlineable. 

   6:         if (_error == null)

   7:             InitializeStdOutError(false);

   8:         return _error;

   9:     } 

  10: }

As you can see, if this is the first time we’re using the stream it will try to initialize it. So let’s see the initialization code (again, for both .NET 4.0 and 4.5).

   1: private static void InitializeStdOutError(bool stdout) 

   2: { 

   3:     // Set up Console.Out or Console.Error.

   4:     lock(InternalSyncObject) { 

   5:         if (stdout && _out != null)

   6:             return;

   7:         else if (!stdout && _error != null)

   8:             return; 

   9: ....

Hmm, that lock seems familiar! This is what’s causing our code to deadlock. Console.ReadKey now locks a synchronization object which gets locked when you attempt to access stderr for the first time. Therefore, the solution is simple, we need to cause Console.Error to initialize before the call to Console.ReadKey.

   1: Console.Error.WriteLine("Initializing stderr to prevent threading issues...");

   2:  

   3: serviceHost.Open();

   4: while (Console.ReadKey().Key != ConsoleKey.Q)

   5: {

   6:     Console.WriteLine("Press Q to exit");

   7: }

Note that this may happen to you even if you’re targeting .NET 4.0, as long as you have .NET 4.5 installed. This is because .NET 4.5 is an in-place replacement for .NET 4.0, so you’re always running with the safer (and deadlock-causing) Console.ReadKey() if you installed the new framework version.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. tietrinnyMay 15, 2013 ב 18:41

    When I originally commented I clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I get four emails using the same comment. Is there any way you possibly can get rid of me from that service? Thanks!

    [URL=http://www.toplululemonoutlet.com/lululemon-tops/lululemon-in-stride-jackets.html]stride jacket lululemon[/URL]

    Reply
  2. tietrinnyMay 15, 2013 ב 19:22

    I discovered your blog web page on google and check a few of your early posts. Continue to keep up the especially great operate. I just further up your RSS feed to my MSN News Reader. Seeking forward to reading much more from you later on!

    [url=http://cheapchinajordans6.webstarts.com/]cheap jordans retro[/url]

    Reply