147

This is something I discovered just a few days ago, I got confirmation that it isn't just limited to my machine from this question.

The easiest way to repro it is by starting a Windows Forms application, add a button and write this code:

    private void button1_Click(object sender, EventArgs e) {
        MessageBox.Show("yada");
        Environment.Exit(1);         // Kaboom!
    }

The program fails after the Exit() statement executes. On Windows Forms you get "Error creating window handle".

Enabling unmanaged debugging makes it somewhat clear what's going on. The COM modal loop is executing and allows a WM_PAINT message to be delivered. That's fatal on a disposed form.

The only facts I've gathered so far are:

  • It isn't just limited to running with the debugger. This also fails without one. Rather poorly as well, the WER crash dialog shows up twice.
  • It doesn't have anything to do with the bitness of the process. The wow64 layer is pretty notorious, but an AnyCPU build crashes the same way.
  • It doesn't have anything to do with the .NET version, 4.5 and 3.5 crash the same way.
  • The exit code doesn't matter.
  • Calling Thread.Sleep() before calling Exit() doesn't fix it.
  • This happens on the 64-bit version of Windows 8, and Windows 7 does not seem to be affected the same way.
  • This should be relatively new behavior, I haven't seen this before. I see no relevant updates delivered through Windows Update, albeit that the update history isn't accurate on my machine any more.
  • This is grossly breaking behavior. You would write code like this in an event handler for AppDomain.UnhandledException, and it crashes the same way.

I'm particularly interested in what you could possibly do to avoid this crash. Particularly the AppDomain.UnhandledException scenario stumps me; there are not a lot of ways to terminate a .NET program. Please do note that calling Application.Exit() or Form.Close() are not valid in an event handler for UnhandledException, so they are not workarounds.


UPDATE: Mehrdad pointed out that the finalizer thread could be part of the problem. I think I'm seeing this and am also seeing some evidence for the 2 second timeout that the CLR gives the finalizer thread to finish executing.

The finalizer is inside NativeWindow.ForceExitMessageLoop(). There's an IsWindow() Win32 function there that roughly corresponds with the code location, offset 0x3c when looking at the machine code in 32-bit mode. It seems that IsWindow() is deadlocking. I cannot get a good stack trace for the internals however, the debugger thinks the P/Invoke call just returned. This is hard to explain. If you can get a better stack trace then I'd love to see it. Mine:

System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.ForceExitMessageLoop() + 0x3c bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Finalize() + 0x16 bytes
[Native to Managed Transition]
kernel32.dll!@BaseThreadInitThunk@12()  + 0xe bytes
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes

Nothing above the ForceExitMessageLoop call, unmanaged debugger enabled.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 2
    I just tried this with .NET 4, 4 Client Profile, 3.5, 3.5 Client Profile, 3.0, and 2.0, and did not receive an error on any of them. 64-bit Windows 7 is my OS, using VS2010. – Steve Aug 03 '13 at 20:17
  • 2
    @Steve `This happens on the 64-bit version of Windows 8` Hans has said so ! – Parimal Raj Aug 03 '13 at 20:18
  • I can't reproduce it on my machine (Win8, 64-bits) using an anonymous function for the event handler. (I've just created a short but complete program - about 10 lines - to try to demonstrate this.) I'm running it from a console - does that change things for you? – Jon Skeet Aug 03 '13 at 20:21
  • 7
    I can repro this (Win 8, 64-bits), copy/pasted your code and wired up a button and I get the exact symptoms described. – keyboardP Aug 03 '13 at 20:25
  • 3
    A console mode app could not demonstrate this problem, nothing can go wrong when Exit() keep pumping messages. – Hans Passant Aug 03 '13 at 20:25
  • Was only able to get the exception once in 20 trials (Win 8 64 bits, VS2k12, .NET4.5) – xanatos Aug 03 '13 at 20:33
  • Strange. I received the error only first time. I thought solution was Thread.Sleep(1) – Vano Maisuradze Aug 03 '13 at 20:36
  • I couldn't reproduce this on Windows 8 x64, VS 2013 using .NET 3.5 or 4.5 (with the debugger attached). – user541686 Aug 03 '13 at 20:44
  • 3
    I have encountered this kind of behavior with `Exit(0)` a bit ago with some 64bit Win7, Changing `ExitCode` doesn't helped now using `Process.GetCurrentProcess().Kill()` without any problem it works – Sriram Sakthivel Aug 03 '13 at 20:48
  • I wonder if filtering out messages after Exit would prevent the crash (do Application.AddMessageFilter, and filter out all messages after Exit, by setting a flag atomically with Exit)? – Eren Ersönmez Aug 03 '13 at 21:27
  • @HansPassant: Can you get a stack trace with ProcExp and check if it's the same as what you see in Visual Studio? You might also try WinDbg to see what's going on.... although a 2-second timeout might not be enough for that... – user541686 Aug 03 '13 at 22:02
  • Without the MessageBox call, I seem to be unable to reproduce this error.. This would leave me to believe Environment.Exit may not be the culprit and its more that the active window handle is still in limbo somewhere? (Environment.Exit just happens to be a call that requires the handle) – Sayse Aug 03 '13 at 22:26
  • @Sayse - yes, the MessageBox call is instrumental to trigger the bug. But you can Sleep() forever after it and not escape the crash. There's some kind of global state getting messed up inside Windows by making that call. Who'd thunk :) – Hans Passant Aug 03 '13 at 22:37
  • I never trust Thread.Sleep for the obvious reasons, putting a timer with a 1ms interval seems to fix it .. `t.Start();t.Tick += (s, ee) => {Environment.Exit(1); // Kaboom!};` (interval and timer declared before messagebox – Sayse Aug 03 '13 at 22:45
  • 1
    @Sayse - I wonder if that's related to exiting on a different thread. I just tried exiting on a background thread (via BackgroundWorker) and it also seems to have fixed it. – keyboardP Aug 03 '13 at 23:10
  • @keyboardP - I could see how this could affect the queue so possibly right.. – Sayse Aug 03 '13 at 23:21
  • @HansPassant: I just tried it on my home machine (Win8 x64 with VS2013), no repro there either. – user541686 Aug 04 '13 at 03:23
  • I imagine you could use p/invoke to call ExitProcess, if you want to be really safe. – Harry Johnston Aug 08 '13 at 01:59
  • This bug is very `volatile`, I can reproduce this sometimes but sometimes it runs OK. For those who can't reproduce this, just try it for several times. – King King Aug 18 '13 at 10:16
  • I can reproduce this (Windows 8 x64, visual studio 2008, .net 2.0). I encountered this error testing internal program which led me to this post (I also tested your code and symptons are the same). I added this.Dispose() before Environment.Exit(1) and for now it is working. I can also confirm King King comment - sometimes it closes correctly and other times it does not. – broadband Nov 11 '13 at 08:01
  • Perhaps this is by design. By definition after a "crash" program execution is over. Why does it matter how it happened? Perhaps you should complain instead that the termination just wasn't clean enough for you... Certainly your program **did** terminate. –  May 06 '14 at 10:27
  • @HansPassant I'm curious -- did you overlook @Sriram's post? `Process.GetCurrentProcess().Kill()` seems like it should work well enough to give you a clean exit. – BrainSlugs83 Jul 08 '14 at 08:27
  • Did you overlook the need to set a process exit code? – Hans Passant Jul 08 '14 at 09:28

4 Answers4

93

I contacted Microsoft about this problem and that seemed to have paid off. At least I'd like to think it did :). Although I didn't get a confirmation of a resolution back from them, the Windows group is difficult to contact directly and I had to use an intermediary.

An update delivered through Windows Update solved the problem. The noticeable 2 second delay before the crash is no longer present, strongly suggesting that the IsWindow() deadlock got solved. And the program shuts down cleanly and reliably. The update installed patches for Windows Defender, wdboot.sys, wdfilter.sys, tcpip.sys, rpcrt4.dll, uxtheme.dll, crypt32.dll and wintrust.dll

Uxtheme.dll is the odd-duck out. It implements the Visual Styles theming API and is used by this test program. I can't be sure, but my money is on that one as the source of the problem. The copy in C:\WINDOWS\system32 has version number 6.2.9200.16660, created on August 14th, 2013 on my machine.

Case closed.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
55

I don't know why it doesn't work "any more", but I think Environment.Exit executes pending finalizers. Environment.FailFast doesn't.

It might be that (for some bizarre reason) you have weird pending finalizers that must run afterward, causing this to happen.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 2
    You might be on to something. The finalizer is busy executing NativeWindow.ForceExitMessageLoop(). Oddly it isn't nested in any call. – Hans Passant Aug 03 '13 at 21:07
  • @HansPassant: I wish I could repro the issue so I could look into it, but I can't. Is the call to `NativeWindow.ForceExitMessageLoop` stuck in managed or unmanaged code? Is it even stuck, or is it busy-waiting or waiting for a message or something else? – user541686 Aug 03 '13 at 21:10
  • This certainly seem to point at the core problem. I think it is the IsWindow() winapi function that is at the root of the problem. I think I'm also seeing the 2 second timeout on the finalizer thread, after which it all goes to hell. The debugger doesn't *show* it executing the IsWindow() call but I've seen Windows play tricks with the stack before, switching it out when entering critical code inside Windows. – Hans Passant Aug 03 '13 at 21:20
  • @HansPassant: Interesting. Yeah, Windows does play tricks with the stack, as well as change behavior when you're running inside a debugger. Do you have any programs installed that might play with window handles in unexpected ways? Say, a hotkey handler of some sort, a program that installs Windows hooks, or something like that? – user541686 Aug 03 '13 at 21:25
  • @HansPassant: Also, is the handle given to `IsWindow` even valid? Might it point to some other window in a different process? – user541686 Aug 03 '13 at 21:26
  • IsWindow() would not have a problem with that. Can you tell me something about how you maintain your machine? Do you have Windows Update enabled? If you do, when was the last time you had an update that required a reboot? – Hans Passant Aug 03 '13 at 21:43
  • @HansPassant: Yes, Windows Update is enabled on this machine. The latest rebooting update I have installed is from 7/16/2013; the only updates I have after that are virus definition updates (latest one is from yesterday). – user541686 Aug 03 '13 at 21:56
  • That's what my update history looks like as well. I know it is incorrect, virus definition updates don't require a reboot and I've had to reboot at least twice since then to get updates installed. Shrugged it off as "they fumbled that one". Pretty bad timing for this bug :( – Hans Passant Aug 03 '13 at 22:04
  • @HansPassant: Weird... at least it means our machines probably have the same update configuration, so the fact that I can't reproduce it means it probably wasn't any update that caused this; something else is different. – user541686 Aug 03 '13 at 22:07
  • 4
    I think the Environment.FailFast() method, for the given case of unhandled exceptions, is probably the best method to use anyway. (I wasn't aware of it - thanks!) However there's a lot of legacy code that would be using Environment.Exit() which will crash awkwardly unfortunately :( – Ian Yates Aug 06 '13 at 23:37
  • 2
    You are most definitely on to something. In my case, I had started an IHost using IHost.StartAsync to perform some integration testing, and yet after calling (and of course awaiting) IHost.StopAsync, the process still didn't terminate. Only after calling IHost.Dispose, the process terminates. Thank you for the tip – Malte R May 07 '20 at 10:09
6

This doesn't explain why it's happening, but I wouldn't call Environment.Exit in a button event handler like your sample - instead close the main form as suggested in rene's answer.

As for an AppDomain.UnhandledException handler, maybe you could just set Environment.ExitCode rather than calling Environment.Exit.

I'm not sure what you're trying to achieve here. Why do you want to return an exit code from a Windows Forms application? Normally exit codes are used by console applications.

I'm particularly interested in what you could possibly do to avoid this crash Calling Environment.Exit() is required to prevent the WER dialog from showing.

Do you have a try/catch in the Main method? For Windows Forms applications I always have a try/catch around the message loop as well as the unhandled exception handlers.

Community
  • 1
  • 1
Joe
  • 122,218
  • 32
  • 205
  • 338
  • Pretty sure you're supposed to call `Application.Exit` instead of `Environment.Exit`. – user541686 Aug 03 '13 at 20:48
  • 7
    Sorry, this is not a workaround. Calling Environment.Exit() is required to prevent the WER dialog from showing. Note the "known fact" as well, the exit code doesn't matter. – Hans Passant Aug 03 '13 at 20:48
  • 8
    @Hans: is catching AppDomain.UnhandledException to try to avoid the WER dialog legitimate in the first place? I mean, if there's an unhandled exception, the WER dialog is *supposed* to show, isn't it? – Harry Johnston Aug 08 '13 at 01:56
4

I've found same problem in our app, we have resolved it with the following construct:

Environment.ExitCode=1;
Application.Exit();
Jesse
  • 3,522
  • 6
  • 25
  • 40
  • 1
    In general, `Application.Exit()` will be less likely to immediately terminate the app compared to `Environment.Exit()` which does not do much work before exiting – FindOutIslamNow Nov 10 '20 at 16:28