OnBeforeNot closed in Debug mode

Having problems with building or using CEF's C/C++ APIs? This forum is here to help. Please do not post bug reports or feature requests here.

OnBeforeNot closed in Debug mode

Postby bpeikes » Wed Jun 29, 2022 4:32 pm

I've been struggling with this issue for several months, and going give a couple of updates now that I think I understand cef a bit better. I'm still not sure why I'm having this issue, but perhaps with a clearer understanding I can figure out the issue.

Using version 102.0.10, which is added to our project using NuGet.
Project is a Win32 64 bit application, using Windows Template Library.
Running CEF with multi_threaded_message_loop = 1
We have a "lightweight" window class CCEFWindow, which derives from CWindowImpl, CefClient, CefLoadHandler and CefLifespanHandler
CCEFWindow has the following header:

Code: Select all
/// <summary>
/// Holds a CEF Browser instance, and implements various CEF callback intefaces
/// </summary>
class CCEFWindow :
   public CWindowImpl<CCEFWindow>,
   public CefClient,
   public CefLoadHandler,
   public CefLifeSpanHandler
{

public:
   CCEFWindow(const char *pName);
   virtual ~CCEFWindow();

   BEGIN_MSG_MAP(CCEFWindow)
      MESSAGE_HANDLER(WM_CREATE, OnCreate)
      MESSAGE_HANDLER(WM_SIZE, OnSize)
      MESSAGE_HANDLER(WM_SIZE, OnClose)
      MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
   END_MSG_MAP()

   LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
   LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
   LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
   LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

   // CefClient Overrides to provide handler interfaces
   virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override;
   virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override;

   // CefLoadHandler
   virtual void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, TransitionType transition_type);

   // CefLifeSpanHandler
   virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;
   virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
   virtual bool DoClose(CefRefPtr<CefBrowser> browser) override;

   void SetBackgroundColor(COLORREF bgColor);
   void LoadURL(const char *pURI);
   std::string GetURL();

   // Call this when you are getting ready to shutdown
   void Destroy();

   bool IsDestroyed();
   void WaitForTerminate();

private:

   void UpdateLayout();

   std::string            m_name;
   std::string            m_initialURL;

   CefRefPtr<CefBrowser>   m_browser;
   std::optional<int>      m_browserID;

   HANDLE               m_browserOnBeforeCloseEvent;

   bool               m_isBrowserClosing;
   bool               m_hasDoCloseRun;
   bool               m_hasOnBeforeClose;

   CefBrowserSettings      m_browserSettings;
   CefWindowInfo         m_cefWinInfo;

   IMPLEMENT_REFCOUNTING(CCEFWindow);
};
We have another window which holds onto a refcounted pointer to a CCEFWindow, which is created this way:
[code]   
m_pwndCEFWindow = new CCEFWindow(AppLocation.path().c_str());
m_pwndCEFWindow->SetBackgroundColor(APP_COLOR_BACKGROUND);
m_pwndCEFWindow->Create(m_hWnd, CRect(CBrowserDefs::MarginX, GetBrowserY(), 800, 600), NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE);[/code]


In the OnCreate method of the CCefWindow, we create the browser this way:
Code: Select all
LRESULT CCEFWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
{
    RECT rc = RECT{ 0 };
    GetClientRect(&rc);

    auto rect = TO_CEF_RECT(rc);
    m_cefWinInfo.SetAsChild(m_hWnd, rect);
   
    CefBrowserHost::CreateBrowser(m_cefWinInfo, this, "", m_browserSettings, nullptr, nullptr);

    return 0;
}

When we get a close message in the most top level window, we make a call which eventually makes its way down to our CCefWindow::Destroy() method, which looks like this:
Code: Select all
void CCEFWindow::Destroy()
{
    if (m_browser && ! m_isBrowserClosing)
    {
        m_browser->GetHost()->CloseBrowser(true);
        m_isBrowserClosing = true;
    }
}


We see that our CCefWindow::DoClose method is getting called, which we return false from:
Code: Select all
bool CCEFWindow::DoClose(CefRefPtr<CefBrowser> browser)
{
    CEF_REQUIRE_UI_THREAD();
    m_hasDoCloseRun = true;
    return false;
}


We see that DoClose has been called, and exit the main message when we get the second WM_CLOSE, see that DoClose was called, and then exit the message loop.
We call CefShutdown, and after a little time get and exception. With the stack:

App.exe!cef::logging::LogMessage::~LogMessage() Line 188 C++
App.exe!shutdown_checker::AssertNotShutdown() Line 32 C++
App.exe!`anonymous namespace'::life_span_handler_on_before_close(_cef_life_span_handler_t * self, _cef_browser_t * browser) Line 211 C++

If I breakpoint right before calling CefShutdown, I'm finding that the thread "CrBrowserMain" , which I believe is the UI thread, is stuck on NtUserMsgWaitForMultipleObjects.

I believe that CrBrowserMain is holding on to a reference to the CCCEFWindow which implements the callbacks because it hasn't called OnBeforeClose yet.

Note, this only happens in debug, and I can loop forever with a one second sleep call, waiting for OnBeforeClose to get called, and it never does.

It seems like the UI thread is waiting for something, but I don't know what.
Last edited by bpeikes on Thu Jun 30, 2022 9:05 am, edited 1 time in total.
bpeikes
Techie
 
Posts: 19
Joined: Mon Mar 21, 2022 1:27 pm

Re: OnBeforeNot closed in Debug mode

Postby magreenblatt » Wed Jun 29, 2022 4:42 pm

You need to wait for OnBeforeClose before exiting the message loop.
magreenblatt
Site Admin
 
Posts: 12379
Joined: Fri May 29, 2009 6:57 pm

Re: OnBeforeNot closed in Debug mode

Postby bpeikes » Thu Jun 30, 2022 6:58 am

How do I wait to exit the message loop? I’ve tried waiting on mutex on the processing of the second WM_CLOSE, but it OnBeforeClose still never gets called.

What should we do in the second WM_CLOSE to wait for OnBeforeClose?
bpeikes
Techie
 
Posts: 19
Joined: Mon Mar 21, 2022 1:27 pm

Re: OnBeforeNot closed in Debug mode

Postby magreenblatt » Thu Jun 30, 2022 8:07 am

How are you running the message loop?
magreenblatt
Site Admin
 
Posts: 12379
Joined: Fri May 29, 2009 6:57 pm

Re: OnBeforeNot closed in Debug mode

Postby bpeikes » Thu Jun 30, 2022 9:03 am

We are running in multi_threaded_message_loop = 1, and using WTL's CMessageLoop class to run the loop for our main window.
bpeikes
Techie
 
Posts: 19
Joined: Mon Mar 21, 2022 1:27 pm

Re: OnBeforeNot closed in Debug mode

Postby magreenblatt » Thu Jun 30, 2022 9:28 am

bpeikes wrote:We are running in multi_threaded_message_loop = 1, and using WTL's CMessageLoop class to run the loop for our main window.

In that case you don't need to explicitly quit CEF's message loop, but you will likely need to keep running the WTL CMessageLoop until OnBeforeClose is called.
magreenblatt
Site Admin
 
Posts: 12379
Joined: Fri May 29, 2009 6:57 pm

Re: OnBeforeNot closed in Debug mode

Postby bpeikes » Wed Jul 06, 2022 4:26 pm

Keeping the main message loop going until OnBeforeClose is called from all Browsers seems to do the trick, but I don't like how it breaks the encapsulation of the code. i.e. Our main frame window shutdown has to know about how CEF works and our code that wraps a browser has to know that OnBeforeClose should post a WM_CLOSE to the toplevel window.

Right now, I have my Window classes own browsers, but it seems like to separate concerns, I should use a browser factory class which can encapsulate the lifetime callbacks and keep track of all browsers created so I know when they have all called OnBeforeClose.Is that what people normally do, i.e. keep a global factory/reference counter for browsers?
bpeikes
Techie
 
Posts: 19
Joined: Mon Mar 21, 2022 1:27 pm


Return to Support Forum

Who is online

Users browsing this forum: No registered users and 28 guests