Page 1 of 1

Crash closing browser

PostPosted: Tue Jun 30, 2020 10:00 am
by joaoneves
I'm getting a random crash when closing a browser instance. The stack is as follows:

Code: Select all
    libcef.dll![thunk]:CefCToCppRefCounted<class CefAppCToCpp,class CefApp,struct _cef_app_t>::Release`vtordisp{4294967292,0}' (void)   C++   Non-user code. Symbols loaded.
>   libcef.dll!CefBrowserHostImpl::~CefBrowserHostImpl() Line 0   C++   Symbols loaded.
    libcef.dll![thunk]:CefBrowserHostImpl::`vector deleting destructor'`adjustor{32}' (unsigned int)   C++   Non-user code. Symbols loaded.
    libcef.dll![thunk]:CefBrowserHostImpl::Release`vtordisp{4294967292,0}' (void)   C++   Non-user code. Symbols loaded.
    libcef.dll!CefCppToCRefCounted<CefBrowserCppToC,CefBrowser,_cef_browser_t>::Release() Line 84   C++   Symbols loaded.
    libcef.dll!CefCppToCRefCounted<CefAuthCallbackCppToC,CefAuthCallback,_cef_auth_callback_t>::struct_release(_cef_base_ref_counted_t * base) Line 165   C++   Symbols loaded.
    [Managed to Native Transition]      Annotated Frame
    Xilium.CefGlue.dll!Xilium.CefGlue.Interop.cef_browser_t.release(Xilium.CefGlue.Interop.cef_browser_t* self) Line 212   C#   Symbols loaded.
    Xilium.CefGlue.dll!Xilium.CefGlue.CefBrowser.Release() Line 102   C#   Symbols loaded.
    Xilium.CefGlue.dll!Xilium.CefGlue.CefBrowser.~CefBrowser() Line 60   C#   Symbols loaded.


I'm releasing the browser instance only once (double checked), but sometimes, not always I get this crash. Since this is on the native side, I'm clueless on what to do to prevent this.
Any help or pointers are appreciated.

Using cef 75.

Re: Crash closing browser

PostPosted: Tue Jun 30, 2020 12:34 pm
by Czarek
What is the full stack trace?

Re: Crash closing browser

PostPosted: Wed Jul 01, 2020 3:27 am
by joaoneves
Thats is the full stack. This is called from the .net finalizer (thread).

This happens frequently when closing the browser right after loading a page.

Re: Crash closing browser

PostPosted: Wed Jul 01, 2020 3:51 am
by Czarek
At what point in app life time are you closing the browser and what is the procedure you follow?

Re: Crash closing browser

PostPosted: Wed Jul 01, 2020 4:09 am
by joaoneves
This happens sometimes is real life scenarios but to reproduce it more frequently I created the following testing scenario:

Code: Select all
Loop 30 times:
   Create browser instance
   Browser on after created is called:
      Wait 1 sec on (app) UI thread - optional (happens with or without this delay)
      Close browser (force = true)
      Browser on closed is called:
         Dispose browser


After the 4th or 5th iteration the crash happens. I reduced the testing conditions to this and I don't even have to load a page.

Re: Crash closing browser

PostPosted: Wed Jul 01, 2020 5:06 am
by ndesktop
You need a shutdown loop, I suppose.
CloseBrowser is not synchronous (someone correct me if I'm wrong), it's the OnBeforeClose callback that tells you the browser is *about to* close.
So this is how I did it:
- DoClose. If browser window was reparented to a tab, main window etc. reparent browser window to something dummy, or remove the child window style (GWL_STYLE: & ~WS_CHILD, | WS_POPUP)
- OnBeforeClose: notify parent object (in my example, I'm calling OnBrowserClosed(browser))
- OnBrowserClosed is implemented in main window (main form in C#) which does specific cleanup, then check if can close or not.
Here are the implementation excerpts.
Client handler:
Code: Select all
LONG ClientHandler::BrowsersAddRef()
{
    CEF_REQUIRE_UI_THREAD();
    return ++m_BrowsersCount;
}
LONG ClientHandler::BrowsersRelease()
{
    CEF_REQUIRE_UI_THREAD();
    return --m_BrowsersCount;
}
bool ClientHandler::HaveBrowsers() const
{
    CEF_REQUIRE_UI_THREAD();
    return m_BrowsersCount != 0;
}
LONG ClientHandler::BrowsersCount() const
{
    CEF_REQUIRE_UI_THREAD();
    return m_BrowsersCount;
}
Note. Call BrowsersAddRef() in OnAfterCreated, BrowsersRelease() in OnBeforeClose.

bool ClientHandler::DoClose(CefRefPtr<CefBrowser> browser)
{
    CEF_REQUIRE_UI_THREAD();
    if(browser.get() != 0)
    {
        HWND hwndHost = browser->GetHost()->GetWindowHandle();
        ::SetParent(hwndHost, NULL);
        DWORD dwHostStyle = (DWORD)::GetWindowLongPtrW(hwndHost, GWL_STYLE);
        if(dwHostStyle & WS_CHILD)
        {
            dwHostStyle &= ~WS_CHILD;
            dwHostStyle |= WS_POPUP;
        }
        ::SetWindowLongPtrW(hwndHost, GWL_STYLE, (LONG_PTR)dwHostStyle);
    }
    return false;
}

void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser)
{
... cleanup
   BrowsersRelease();
  _NotifyBrowserClosed(browser);
}
void ClientHandler::_NotifyBrowserClosed(CefRefPtr<CefBrowser> browser)
{
    if(!CefCurrentlyOn(TID_UI)) {
        // Execute this method on the main thread.
        CefCreateClosureTask(base::Bind(&ClientHandler::_NotifyBrowserClosed, this, browser));
        return;
    }
    m_pParent->OnBrowserClosed(browser);
}
Note: m_pParent is the main application window.


Main app window:
Code: Select all
void MainWindow::OnBrowserClosed(CefRefPtr<CefBrowser> browser)
{
    CEF_REQUIRE_UI_THREAD();
...
    OnBrowserWindowDestroyed(browser);
    return;
}
void MainWindow::OnBrowserWindowDestroyed(CefRefPtr<CefBrowser> browser)
{
... specific cleanup
    browsers_destroyed_ = !_handler->HaveBrowsers(); // browser count is NOT > 0 ?
    if(browsers_destroyed_) { // yes, we have 0 browsers
        if (!window_destroyed_) { // main window not destroyed
            if(_handler->isClosing()) { // isClosing returns true if we entered the shutdown phase, for example click the Close button on main window
                CloseIfNeeded(true); // force close main window
            }
        }
    }
    NotifyDestroyedIfDone();
}
Note. browsers_destroyed_ and window_destroyed_ are bools, first indicates "all browsers are destroyed, browser count is 0" and the second means "main window is destroyed".

void MainWindow::CloseIfNeeded(bool force)
{
    if (GetSafeHwnd()) {
        if (force) {
            ::DestroyWindow(GetSafeHwnd());
        }
        else {
            ::PostMessage(GetSafeHwnd(), WM_CLOSE, 0, 0);
        }
    }
}
void MainWindow::OnDestroyed()
{
    window_destroyed_ = true;
    NotifyDestroyedIfDone();
}
Note. OnDestroyed is called from WM_NCDESTROY.

void MainWindow::NotifyDestroyedIfDone()
{
    // (re)check if browsers destroyed
    browsers_destroyed_ = !_handler->HaveBrowsers();
    if(window_destroyed_ && browsers_destroyed_) {
        OnWindowDestroyed();
    }
}
void MainWindow::OnWindowDestroyed()
{
    Application::QuitMessageLoop();
}

void Application::QuitMessageLoop()
{
  CefDoMessageLoopWork();
  CefQuitMessageLoop();
}


The basic idea is this (also consider that not all things might be helpful for you, I'm describing my particular case):
- when WM_CLOSE is first sent, we don't close (return TRUE from WM_CLOSE), mark the client handler as closing (ClientHandler::isClosing will return true from now on), mark main window as closing (we enter in a shutdown phase), then post UWM_CLOSE to the main window (UWM_CLOSE is an user-defined message)
- UWM_CLOSE arrives; do the cleanup (hide main window, dispose resources etc.) and arm an one time shutdown timer
- WM_TIMER: if timer ID == shutdown timer ID, check the ClientHandler::BrowserCount(); if 0, destroy main window and call Application::QuitMessageLoop(), which consists of two calls, a last CefDoMessageLoopWork() + CefQuitMessageLoop()
- if browser count is not 0, rearm the one time shutdown timer and next timer will recheck if browser count dropped to 0.
How you pick how much time or how many times you rearm the shutdown timer is up to you.

Re: Crash closing browser

PostPosted: Wed Jul 01, 2020 3:20 pm
by joaoneves
I think I found a Ref count bug on cefglue that was causing the crash.

Re: Crash closing browser

PostPosted: Mon Nov 02, 2020 7:57 pm
by weq
@joanoneves
@ndesktop

Can u give me some details of this refcount bug? linked issue? would like to apply this patch to my codebase.
Im currently struggling with a shutdown crash in my macos app which uses cefglue under the hood, and i am getting similiar types of failurerates as joanoneves but different stacks.
crash1.PNG
crash1.PNG (188.33 KiB) Viewed 24187 times


After reviewing this post i am thinking i may need to apply this reparenting trick to my app also. Im not very experienced in this area, first time dev'ing on a mac too :)

I have a repo here you can clone to see all the details...
https://github.com/captainjono/Chromely ... -720553218