Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environment.Exit() causes the application to hang #990

Closed
jamespearce2006 opened this issue Apr 30, 2015 · 37 comments
Closed

Environment.Exit() causes the application to hang #990

jamespearce2006 opened this issue Apr 30, 2015 · 37 comments

Comments

@jamespearce2006
Copy link
Contributor

My WPF application catches unhandled dispatcher exceptions, notifies the user, logs the stack trace, then calls Environment.Exit() to shut the application down immediately.

Unfortunately, with CefSharp, calling Environment.Exit() causes the application to totally lockup. In fact, it locks up so badly that I cannot attach a debugger. Calling it with the debugger attached also causes Visual Studio to lock up.

The same issue affects the WpfExample.

Calling Environment.FailFast works, but unfortunately shows Microsoft's own exception dialog.
The only solution I can think of is to kill the current process, but this seems messy.

@amaitland
Copy link
Member

Are you calling Cef.Shutdown()?

@jankurianski
Copy link
Member

Hi James, there are a bunch of things for you to check and try here: #800 (comment)

Having used Environment.Exit() myself, I believe the answer is calling Shutdown() manually.

@jankurianski
Copy link
Member

@amaitland Too quick 😄

@amaitland
Copy link
Member

@jankurianski Yours is a more detailed answer 😄

@amaitland amaitland added the wpf label Apr 30, 2015
@amaitland amaitland changed the title CefSharp.Wpf: Environment.Exit() causes the application to hang Environment.Exit() causes the application to hang Apr 30, 2015
@amaitland
Copy link
Member

@jamespearce2006 Can you confirm which version your using?

The same issue affects the WpfExample.

Can you provide the updated example for demo purposes?

@jamespearce2006
Copy link
Contributor Author

The problem with calling Cef.Shutdown is that the area of code that catches the unhandled exception is not one that should know about Cef, or Web browsers in general. In fact, I would say I shouldn't need to do any cleanup before calling Environment.Exit - I've already decided my app is in a bad state, and I want to quit as quickly as possible.

It's trivially easy to modify the CefSharp.Wpf.Example.
Modify BrowserTabViewModel.cs, and replace the line in the constructor thusly:

//HomeCommand = new DelegateCommand(() => AddressEditable = Address = CefExample.DefaultUrl);
HomeCommand = new DelegateCommand(() => Environment.Exit(-1));

Compile, and run, and click the "Home" button, and observe the app hang permanently.

James

@amaitland
Copy link
Member

In fact, I would say I shouldn't need to do any cleanup before calling Environment.Exit - I've already decided my app is in a bad state, and I want to quit as quickly as possible.

Any how exactly do you expect Cef to shutdown gracefully if you don't tell it?

Calling Environment.Exit() is nasty, it's like killing off the process, the default exit handling CefSharp uses is bypassed. If you can use Application.Current.Shutdown();

@amaitland
Copy link
Member

(We are spending our free time trying to help you, so a little less attitude would also go a long way).

@jamespearce2006
Copy link
Contributor Author

Huh? No attitude here - just want to get it working... Apologies if my text is terse. The CefSharp project is great - I just want to make it better :-)

I completely agree that Environment.Exit isn't the way to gracefully shutdown my app. However...

When I call Environment.Exit, I don't particularly care if it shuts down gracefully - I just want to quit with maximum prejudice. My app is already in a bad place - I don't really know what state it's in, I just want to put it out of its misery.

Regardless, I would say blocking Environment.Exit from completing is probably not good form.

@amaitland
Copy link
Member

Regardless, I would say blocking Environment.Exit from completing is probably not good form.

It's more complicated than just exiting your single application, there are sub processes that need to be notified.

If Cef.Initialize() is called, then at some stage Cef.Shutdown() must be called.

@rassilon
Copy link
Contributor

In WinForms I used Application.Exit instead of Environment.Exit and that did the correct thing for simple test unhandled exceptions. Does WPF have something equivalent?

@jamespearce2006
Copy link
Contributor Author

I understand there are other processes, but what happens if the main process crashes? Surely they clean themselves up at some point anyway... What I'm really attempting to do is something equivalent to an app crash without the annoying Microsoft "Looking for solutions..." dialog.

Calling Environment.Exit() does actually call Cef::Shutdown anyway, it seems. I see ParentProcessExitHandler in Cef.h getting called before my app hangs.

Calling Cef.Shutdown before Environment.Exit() does work in normal conditions, however from a DispatcherUnhandlerException handler, calling Cef.Shutdown throws a Corrupted State Exception, wierdly...

@amaitland
Copy link
Member

Calling Cef.Shutdown before Environment.Exit() does work in normal conditions, however from a DispatcherUnhandlerException handler, calling Cef.Shutdown throws a Corrupted State Exception, wierdly...

In an attempt to cleanup unmanaged resources the Cef class keeps an instance of every active ChromiumWebBrowser, if there are instances still alive when Shutdown is called it will attempt to dispose them.

@jamespearce2006
Copy link
Contributor Author

It looks like Cef.Shutdown needs to happen on the UI thread too, as the Dispose on the ChromiumWebBrowser access DependencyProperties.

If there is no UI dispatcher any more, things get a little tricky...

I think in my case, the workaround is to call

Process.GetCurrentProcess().Kill();

That seems to be extreme enough to get the job done. :-) (how do I add a smiley face)

@jamespearce2006
Copy link
Contributor Author

I do sometimes see CefShutdown hang, even when attempting to shutdown gracefully. Is there something I need to ensure I do before calling this?

Would undisposed callbacks cause this?

@jamespearce2006
Copy link
Contributor Author

In response to my question above, the issue occurs when running in Debug mode. The hang happens because a breakpoint is hit in CefShutdown. I don't know why this doesn't trigger Visual Studio to do something (perhaps it's the mix of managed vs unmanaged).

Anyway, I see no hangs running in Release mode.

@rassilon
Copy link
Contributor

rassilon commented May 1, 2015

int 3 is usually an assertion failure in CEF. If you can get a stacktrace,
you ought to be able to determine why CEF hit the int 3.

Bill

On Fri, May 1, 2015 at 6:19 AM, jamespearce2006 notifications@github.com
wrote:

In response to my question above, the issue occurs when running in Debug
mode. The hang happens because a breakpoint is hit in CefShutdown. I don't
know why this doesn't trigger Visual Studio to do something (perhaps it's
the mix of managed vs unmanaged).

Anyway, I see no hangs running in Release mode.


Reply to this email directly or view it on GitHub
#990 (comment).

@MattGerg
Copy link

I think in my case, the workaround is to call
Process.GetCurrentProcess().Kill();

Just wanted to corroborate this workaround. Using CefSharp 41.0.1, that has been the most reliable way to terminate our WinForms application.

I've tried many different iterations, sequences, timings, and threadings of Application.Exit(), Environment.Exit(0), and Cef.Shutdown(). Each have caused the application to hang at some point, for some user, on some machine. But I've never been able to consistently reproduce the hang.

@amaitland
Copy link
Member

Using CefSharp 41.0.1, that has been the most reliable way to terminate our WinForms application.

When you say terminate, are you meaning when you exit gracefully? Or just when you need to exit in a hurry and don't care about being graceful?

@MattGerg
Copy link

When you say terminate, are you meaning when you exit gracefully? Or just when you need to exit in a hurry and don't care about being graceful?

Gracefully. Its especially important for us because our application is a single-instance application. Which means the user cannot start a new instance if there is a zombie.

Here's exactly what we're doing now, which seems to be working well:

  1. User chooses to exit the application.
  2. In code, we start up a Thread with IsBackground=True. The thread sleeps for 30 seconds, then calls Process.GetCurrentProcess().Kill();.
  3. In code, we allow the application to exit on its own. This is akin to Application.Exit, but in our case, we just close the main Form of the application.
  4. An event handler calls Cef.Shutdown() in response to the Application.ApplicationExit event.
  5. Most of the time, the application exits cleanly on its own, and so the background thread is terminated while it is still sleeping. In rare cases, when the application hangs, the background thread kills the process after 30 seconds.

Let me also add that we do an absurd amount of Javascript/.NET interaction. So it could very well be that we are somehow causing the hang on our end. Mostly, I just wanted to post a viable workaround for anybody else who may find it useful.

Finally, big thanks to the CefSharp team for providing an awesome library!

@jankurianski
Copy link
Member

There are other reports of this problem here, worth looking at it to see if you do similar things in your app: #820

Check out Bill's comment here in this other issue for another type of problem that can occur if you create COM objects in CEF threads: #872 (comment)

And then a couple of comments further below that, Bill makes a comment about how to use WinDbg to diagnose what is causing the shutdown hang.

@jankurianski
Copy link
Member

I've added a new section to the Troubleshooting page on the wiki about this problem:
https://github.com/cefsharp/CefSharp/wiki/Trouble-Shooting#application-freezing-on-shutdown

Anyone can edit this page so feel free to add debugging tips.

@MattGerg
Copy link

  1. Most of the time, the application exits cleanly on its own, and so the background thread is terminated while it is still sleeping. In rare cases, when the application hangs, the background thread kills the process after 30 seconds.

And of course, the day I post the above... we had another reported hang. So it almost seems like the application shutdown enough to terminate background threads, and then hanged after that point.

I've now resorted to calling Process.GetCurrentProcess().Kill() inline, vs waiting on a background thread. For posterity, I'll report back if that does eliminate the hanging.

@maynardflies
Copy link

EnvironmentExitIssueReproduced.zip
Here are my two cents with this hang.

We use a core framework which doesn't give us a whole lot of control over how the application is exited. The core invokes Environment.Exit(0) to exit the application. Right or wrong method aside, this is how they are doing the exit.

Doing this unfortunately causes Cef to get shut down on a background thread because Cef automatically subscribes ParentProcessExitHandler and for some reason, even when Environment.Exit is called on the UI thread, the eventhandler is raised on a BG thread, which causes a x-thread exception, which as expected causes issues when exiting

So instead, I set "shutdownOnExit" to FALSE when initializing, and subscribed to that event myself. In the event handler, I dispatch the call to Cef.Shutdown to the UI thread.

However, this STILL causes a permanent hang, but this time there seems to be no associated exception. Stack traces from both my process and the browser subprocesses seem to indicate that they are in a wait of some sort, but I haven't figured out why yet.

Is it possible that this can all be avoided by Cef dispatching to the UI thread inside the Shutdown() method (or perhaps tracking the initializer thread, whatever)? I have modified the minimal example to illustrate what's happening. Click the button at the bottom with the text "Environment.Exit(0)" to reproduce.

Regardless of how I try to clean this stuff up, if I don't call Cef.Shutdown BEFORE calling Environment.Exit, my shutdown will fail. The built in shutdown in Cef fails because it's not properly dispatched to the dispatcher thread for what it's trying to dispose of, and trying to shut it down manually by dispatching it causes an inexplicable permanent hang.

Given these details, can we re-open this issue please?

@amaitland
Copy link
Member

We use a core framework which doesn't give us a whole lot of control over how the application is exited. The core invokes Environment.Exit(0) to exit the application. Right or wrong method aside, this is how they are doing the exit.

Environment.Exit is very abrupt, I'm not sure the UI thread is still functional enough to call Cef.Shutdown.

Is it possible that this can all be avoided by Cef dispatching to the UI thread inside the Shutdown() method (or perhaps tracking the initializer thread, whatever)? I have modified the minimal example to illustrate what's happening. Click the button at the bottom with the text "Environment.Exit(0)" to reproduce.

How would this be any different than what you've tried already? Additionally you'll need to set CefSharpSettings.ShutdownOnExit = false

https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp/CefSharpSettings.cs#L40

Given these details, can we re-open this issue please?

For what purpose? What are you hoping to get out of reopening this? If you submit a PR you can reference a closed issue.

Please no zip files. Push your changes to MinimalExample to GitHub please. Also please read https://github.com/cefsharp/CefSharp/blob/master/CONTRIBUTING.md#help-us-help-you

@amaitland
Copy link
Member

I Should also point out that Cef.Initialzie and Cef.Shutdown are framework agnostic, any threading considerations would need to be implemented at a higher level. (Has no reference to either WinForms or WPF, and doesn't known which your using either).

@maynardflies
Copy link

How would this be any different than what you've tried already? Additionally you'll need to set CefSharpSettings.ShutdownOnExit = false

Fair point, I realized this after I posted. I do set that property to false and then do the Shutdown() myself on the ProcessExit() event handler, dispatching it to the UI thread.

For what purpose? What are you hoping to get out of reopening this? If you submit a PR you can reference a closed issue.

I just want to get this issue resolved. This is a bug that's causing problems using the library and I guess I'm hoping that we can continue trying to resolve it

Please no zip files. Push your changes to MinimalExample to GitHub please

I'm having a lot of trouble trying to push changes to Github from behind my corporate proxy. The machines are super locked down. If absolutely necessary I can do so by hand in the web editor, but I think we already understand what is necessary to reproduce the issue.

I Should also point out that Cef.Initialzie and Cef.Shutdown are framework agnostic, any threading considerations would need to be implemented at a higher level.

Okay, fair enough. The odd threading behaviour in this case is coming from WPF itself i.e. raising the ProcessExit event handler on a BG thread, but I dont think that's really what's at issue here

Is this an issue that is going to be investigated? Maybe it was closed because this particular case wasn't visible yet or something?

@amaitland
Copy link
Member

This is a bug that's causing problems using the library and I guess I'm hoping that we can continue trying to resolve it

From what I can tell it's more of a limitation than a bug, I may be mistaken there.

Is this an issue that is going to be investigated?

Investigated by who exactly?

@amaitland
Copy link
Member

and trying to shut it down manually by dispatching it causes an inexplicable permanent hang.

Have you Disposed of all the open ChromiumWebBrowser instances before calling Shutdown?

Can easily add something like Cef.UnmanagedShutdown which simply wraps CefShutdown, then you'd have access to the raw API call.

@amaitland
Copy link
Member

I'm having a lot of trouble trying to push changes to Github from behind my corporate proxy. The machines are super locked down. If absolutely necessary I can do so by hand in the web editor, but I think we already understand what is necessary to reproduce the issue.

If you think it's of benefit then you can just paste the relevant parts as a Gist

@amaitland
Copy link
Member

@maynardflies
Copy link

From what I can tell it's more of a limitation than a bug, I may be mistaken there.

The reason I say it's a bug is just because Environment.Exit should be a legitimate way to tear down the application, albeit forcefully, and CefSharp (or Cef?) causes it to hang instead, ostensibly because it's not handling the process exit mechanism appropriately

Investigated by who exactly?

Presumably, the people like yourself who know the source better than I do. I'm not trying to be adversarial here but I feel this issue is legitimate and should be open

@maynardflies
Copy link

See cefsharp/CefSharp.MinimalExample@52c4e97

Checked this out. You're calling Environment.Exit after Cef.Shutdown, which works fine. The issue is when Environment.Exit is called FIRST, and Cef.Shutdown is called from the AppDomain.Current.ProcessExit Event handler (or not at all), or anywhere after Exit is called.

The idea being that you don't always have control over when Environment.Exit is called, and you can't always call Cef.Shutdown before that happens, so you should be able to react to it happening instead without hanging

@maynardflies
Copy link

If you think it's of benefit then you can just paste the relevant parts as a Gist

Great idea. Attached as a Gist. https://gist.github.com/maynardflies/b74f2d845311d70fefae

Edit: Finally got the commit to work: cefsharp/CefSharp.MinimalExample#23

@maynardflies
Copy link

Have you Disposed of all the open ChromiumWebBrowser instances before calling Shutdown?

Tried this, same issue I'm afraid

@amaitland
Copy link
Member

The reason I say it's a bug is just because Environment.Exit should be a legitimate way to tear down the application, albeit forcefully, and CefSharp (or Cef?) causes it to hang instead, ostensibly because it's not handling the process exit mechanism appropriately

Remember it's a unmanaged C++ that we're dealing with, it doesn't always play by the .Net rules. Chromium it's self has some quirks when it shutsdown, leaves it's self in a dirty state, you cannot reinitialize because of this.

Presumably, the people like yourself who know the source better than I do. I'm not trying to be adversarial here but I feel this issue is legitimate and should be open

I've tried, gotten no where. By all means dig into the issue further, if you come up with something that looks promising we can reopen this 👍