mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-18 18:56:25 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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" };
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user