Fix DesktopWindowXamlSource related crashes when closing a window (#15338)

XAML/WinUI may pump the event loop internally. One of the functions
that does this right now is `DesktopWindowXamlSource::Close()`.

This is problematic in the previous code, because we'd set `_window`
to `nullptr` before calling `Close()` and so any of the `IslandWindow`
callbacks may be invoked during shutdown, which then try to potentially
access `_window` and end up crashing. This commit fixes the issue by
simply not nulling out the `_window` and calling `Close()` directly.

Furthermore, `NonClientIslandWindow` may directly access WinUI
objects in its message handlers which also crashes.

I've had this happen roughly ~1% of my test exits in a debug build
and every single time on a (artificial) very slow CPU.

## Validation Steps Performed
* Closing a window destroys the rendering instance 
This commit is contained in:
Leonard Hecker
2023-05-15 16:11:22 +02:00
committed by GitHub
parent 3d737214a4
commit ba39db52d7
9 changed files with 35 additions and 54 deletions

View File

@@ -2776,9 +2776,8 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - sender (not used)
// - args: the arguments specifying how to set the display status to ShowWindow for our window handle
winrt::fire_and_forget TerminalPage::_ShowWindowChangedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
void TerminalPage::_ShowWindowChangedHandler(const IInspectable& /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
{
co_await resume_foreground(Dispatcher());
_ShowWindowChangedHandlers(*this, args);
}

View File

@@ -507,7 +507,7 @@ namespace winrt::TerminalApp::implementation
void _updateAllTabCloseButtons(const winrt::TerminalApp::TabBase& focusedTab);
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
void _ShowWindowChangedHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);

View File

@@ -65,20 +65,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
std::placeholders::_2);
_window->SetCreateCallback(pfn);
// Event handlers:
// MAKE SURE THEY ARE ALL:
// * winrt::auto_revoke
// * revoked manually in the dtor before the window is nulled out.
//
// If you don't, then it's possible for them to get triggered as the app is
// tearing down, after we've nulled out the window, during the dtor. That
// can cause unexpected AV's everywhere.
//
// _window callbacks don't need to be treated this way, because:
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like this)
// * This particular bug scenario applies when we've already freed the window.
//
// (Most of these events are actually set up in AppHost::Initialize)
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
_window->WindowMoved({ this, &AppHost::_WindowMoved });
@@ -91,21 +77,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
_window->MakeWindow();
}
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
// revoke ALL our event handlers. There's a large class of bugs where we
// might get a callback to one of these when we call app.Close() below. Make
// sure to revoke these first, so we won't get any more callbacks, then null
// out the window, then close the app.
_revokers = {};
_showHideWindowThrottler.reset();
_window = nullptr;
}
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
{
if (_windowLogic)
@@ -443,6 +414,15 @@ void AppHost::Initialize()
_window->OnAppInitialized();
}
void AppHost::Close()
{
// After calling _window->Close() we should avoid creating more WinUI related actions.
// I suspect WinUI wouldn't like that very much. As such unregister all event handlers first.
_revokers = {};
_showHideWindowThrottler.reset();
_window->Close();
}
// Method Description:
// - Called every time when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
@@ -498,7 +478,7 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
// event handler finishes.
_windowManager.SignalClose(_peasant);
_window->Close();
PostQuitMessage(0);
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
@@ -1110,10 +1090,7 @@ void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&
// should prevent scenarios where the Terminal window state and PTY window
// state get de-sync'd, and cause the window to minimize/restore constantly
// in a loop.
if (_showHideWindowThrottler)
{
_showHideWindowThrottler->Run(args.ShowOrHide());
}
_showHideWindowThrottler->Run(args.ShowOrHide());
}
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,

View File

@@ -13,11 +13,11 @@ public:
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
~AppHost();
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args);
void Initialize();
void Close();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);

View File

@@ -37,7 +37,16 @@ IslandWindow::IslandWindow() noexcept :
IslandWindow::~IslandWindow()
{
_source.Close();
Close();
}
void IslandWindow::Close()
{
if (_source)
{
_source.Close();
_source = nullptr;
}
}
HWND IslandWindow::GetInteropHandle() const
@@ -89,17 +98,6 @@ void IslandWindow::MakeWindow() noexcept
WINRT_ASSERT(_window);
}
// Method Description:
// - Called when no tab is remaining to close the window.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::Close()
{
PostQuitMessage(0);
}
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the proper size.

View File

@@ -20,7 +20,7 @@ public:
virtual ~IslandWindow() override;
virtual void MakeWindow() noexcept;
void Close();
virtual void Close();
virtual void OnSize(const UINT width, const UINT height);
HWND GetInteropHandle() const;

View File

@@ -26,7 +26,13 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme)
{
}
NonClientIslandWindow::~NonClientIslandWindow() = default;
void NonClientIslandWindow::Close()
{
// Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource
// inside `IslandWindow::Close()`. XAML thanks us for doing that by not crashing. Thank you XAML.
SetWindowLongPtr(_dragBarWindow.get(), GWLP_USERDATA, 0);
IslandWindow::Close();
}
static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" };

View File

@@ -30,8 +30,8 @@ public:
static constexpr const int topBorderVisibleHeight = 1;
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
virtual ~NonClientIslandWindow() override;
virtual void Close() override;
void MakeWindow() noexcept override;
virtual void OnSize(const UINT width, const UINT height) override;

View File

@@ -42,7 +42,8 @@ int WindowThread::RunMessagePump()
void WindowThread::RundownForExit()
{
_host = nullptr;
_host->Close();
// !! LOAD BEARING !!
//
// Make sure to finish pumping all the messages for our thread here. We