Repository: microsoft/terminal (language: cpp)

Issue:
Crash when quickly closing tabs with the middle mouse button
### Windows Terminal version

1.23.10353.0

### Windows build number

_No response_

### Other Software

_No response_

### Steps to reproduce

* Open 3 or more tabs
* Go ham on that middle mouse button closing the left-most tabs as quickly as you can

### Expected Behavior

_No response_

### Actual Behavior

My suspicion is that me removing async handling from our UI stack (to fix AVs on teardown) causes this AV in WinUI, because now it calls into already dead UI objects. There's absolutely nothing terminal-related in the callstack so I think we should squarely consider this a WinUI bug.

> Exception thrown at 0x00007FF9B35AA2ED (Windows.UI.Xaml.dll) in WindowsTerminal.exe: 0xC0000005: Access violation reading location 0x000000000000031F.

```
[Inline Frame] Windows.UI.Xaml.dll!_xvector_base<REQUEST *>::get_item(unsigned int) Line 588	C++
Windows.UI.Xaml.dll!CEventManager::RaiseHelper(xvector<REQUEST *> * pRegisteredRequests, EventHandle hEvent, CDependencyObject * pSender, CEventArgs * pArgs, int bRefire, HRESULT(*)(void *, CDependencyObject *, EventHandle, CDependencyObject *, CEventArgs *, int, IScriptObject *, HRESULT(*)(CDependencyObject *, CEventArgs *)) pfnScriptCallback, unsigned int & bFired, CDependencyObject * pSenderOverride) Line 983	C++
Windows.UI.Xaml.dll!CEventManager::Raise(EventHandle hEvent, int bRefire, CDependencyObject * pSender, CEventArgs * pArgs, bool fRaiseSync, bool fInputEvent, bool bAllowErrorFallback, CDependencyObject * pSenderOverride) Line 902	C++
[Inline Frame] Windows.UI.Xaml.dll!CEventManager::RaiseRoutedEventBubbling(EventHandle pArgs, CDependencyObject *) Line 1331	C++
Windows.UI.Xaml.dll!CEventManager::RaiseRoutedEvent(EventHandle hEvent, CDependencyObject * pSource, CRoutedEventArgs * pArgs, bool bIgnoreVisibility, bool fRaiseSync, bool fInputEvent, CDependencyObject * coerceToHandledAtElement, RoutingStrategy strategy) Line 1241	C++
Windows.UI.Xaml.dll!CInputServices::RaiseDelayedPointerUpEvent(TouchInteractionMsg * pMsgGesture, CDependencyObject * pElement) Line 2668	C++
Windows.UI.Xaml.dll!CInputServices::ProcessGestureInput(CDependencyObject * pElement, TouchInteractionMsg * pMsg) Line 2419	C++
Windows.UI.Xaml.dll!CInputServices::ProcessTouchInteractionCallback(const xref_ptr<CUIElement> & element, TouchInteractionMsg * message) Line 2315	C++
[Inline Frame] Windows.UI.Xaml.dll!CCoreServices::ProcessTouchInteractionCallback(const xref_ptr<CUIElement> &) Line 1018	C++
Windows.UI.Xaml.dll!GestureRecognizerAdapter::OnTapped(ElementGestureTracker * elementGestureTracker, Windows::UI::Input::IGestureRecognizer * args, Windows::UI::Input::ITappedEventArgs *) Line 163	C++
[Inline Frame] Windows.UI.Xaml.dll!GestureRecognizerAdapter::Init::__l3::<lambda_c84124b77aa695306b3da5613df6029b>::operator()(Windows::UI::Input::IGestureRecognizer *) Line 31	C++
Windows.UI.Xaml.dll!Microsoft::WRL::Details::DelegateArgTraits<long (__cdecl Windows::Foundation::ITypedEventHandler_impl<Windows::Foundation::Internal::AggregateType<Windows::UI::Input::GestureRecognizer *,Windows::UI::Input::IGestureRecognizer *>,Windows::Foundation::Internal::AggregateType<Windows::UI::Input::TappedEventArgs *,Windows::UI::Input::ITappedEventArgs *>>::*)(Windows::UI::Input::IGestureRecognizer *,Windows::UI::Input::ITappedEventArgs *)>::DelegateInvokeHelper<Microsoft::WRL::Implements<Microsoft::WRL::RuntimeClassFlags<2>,Windows::Foundation::ITypedEventHandler<Windows::UI::Input::GestureRecognizer *,Windows::UI::Input::TappedEventArgs *>,Microsoft::WRL::FtmBase>,<lambda_c84124b77aa695306b3da5613df6029b> &,-1,Windows::UI::Input::IGestureRecognizer *,Windows::UI::Input::ITappedEventArgs *>::Invoke(Windows::UI::Input::IGestureRecognizer * <args_0>, Windows::UI::Input::ITappedEventArgs * <args_1>) Line 354	C++
Windows.UI.dll!Microsoft::WRL::InvokeTraits<-2>::InvokeDelegates<class <lambda_067df25809bf24dbfa2d3e63ebcc273a>,struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Input::GestureRecognizer *,class Windows::UI::Input::ManipulationUpdatedEventArgs *> >(class <lambda_067df25809bf24dbfa2d3e63ebcc273a>,class Microsoft::WRL::Details::EventTargetArray *,class Microsoft::WRL::EventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Input::GestureRecognizer *,class Windows::UI::Input::ManipulationUpdatedEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> > *)	Unknown
Windows.UI.dll!Microsoft::WRL::EventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Input::GestureRecognizer *,class Windows::UI::Input::ManipulationUpdatedEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> >::DoInvoke<class <lambda_067df25809bf24dbfa2d3e63ebcc273a> >(class <lambda_067df25809bf24dbfa2d3e63ebcc273a>)	Unknown
Windows.UI.dll!Windows::UI::Input::GestureRecognizerServer::OnTappedEvent(enum Windows::Devices::Input::PointerDeviceType,struct Windows::Foundation::Point,unsigned int,unsigned int)	Unknown
Windows.UI.dll!Windows::UI::Input::GestureRecognizerServer::OnOutput(struct INTERACTION_CONTEXT_OUTPUT2 const *)	Unknown
Windows.UI.dll!Windows::UI::Input::GestureRecognizerServer::OutputCallback(void *,struct INTERACTION_CONTEXT_OUTPUT2 const *)	Unknown
ninput.dll!COutputConverter::Process(class CInteractionContextConfiguration const &,enum InteractionContextProperties::MeasurementUnits,enum CInteractionContextGlobal::FeedbackSettings,struct DigitizerInfo const *,struct INTERACTION_INPUT const *,unsigned int,unsigned int,unsigned int,struct INTERACTION_OUTPUT const &,int,struct HINTERACTIONCONTEXT__ *)	Unknown
ninput.dll!CInteractionContextImpl::OutputCallback(void *,struct INTERACTION_OUTPUT const *)	Unknown
ninput.dll!CInteractionGroupingFilter::_SendOutput(struct INTERACTION_OUTPUT const &)	Unknown
ninput.dll!CInteractionGroupingFilter::Input(void *,struct INTERACTION_OUTPUT const *)	Unknown
ninput.dll!COutputCoalescingFilter::Flush(void)	Unknown
ninput.dll!COutputCoalescingFilter::Input(void *,struct INTERACTION_OUTPUT const *)	Unknown
ninput.dll!CInteractionEngineImpl::DigitizerInput(enum DIGITIZER_KIND,unsigned __int64,unsigned int,unsigned int,struct INTERACTION_INPUT const *)	Unknown
ninput.dll!CInteractionContextImpl::ProcessFrameHistory(unsigned int,unsigned int,struct tagPOINTER_INFO const *)	Unknown
ninput.dll!CInteractionContextImpl::ProcessBufferedPackets(void)	Unknown
ninput.dll!ProcessBufferedPacketsInteractionContext()	Unknown
Windows.UI.dll!Windows::UI::Input::GestureRecognizerServer::ProcessUpEvent(struct Windows::UI::Input::IPointerPoint *)	Unknown
Windows.UI.Xaml.dll!ElementGestureTracker::ProcessPointerInformation(const InputMessage & msg, Windows::UI::Input::IPointerPointStatics * const pointerPointStatics) Line 754	C++
Windows.UI.Xaml.dll!ElementGestureTracker::ProcessPointerMessage(const InputMessage & msg, Windows::UI::Input::IPointerPointStatics * const pointerPointStatics) Line 91	C++
Windows.UI.Xaml.dll!CInputServices::ProcessPointerMessagesWithInteractionEngine(unsigned int pMsg, InputMessage * pInteractionElement, CUIElement * pManipulationElement, CUIElement * bIgnoreManipulationElement, unsigned int pInteractionContext, ElementGestureTracker * bForceDisableGesture, unsigned int) Line 1997	C++
Windows.UI.Xaml.dll!CInputServices::ProcessInteractionPointerMessages(unsigned int pointerId, InputMessage * pMsg) Line 1759	C++
Windows.UI.Xaml.dll!ContentRootInput::PointerInputProcessor::ProcessPointerInput(InputMessage * pMsg, int * handled) Line 718	C++
Windows.UI.Xaml.dll!CInputServices::ProcessInput(InputMessage * pMsg, CContentRoot * contentRoot, int * handled) Line 888	C++
[Inline Frame] Windows.UI.Xaml.dll!CCoreServices::ProcessInput(InputMessage *) Line 992	C++
Windows.UI.Xaml.dll!CXcpBrowserHost::HandleInputMessage(unsigned int uMsg, MsgPacket * pMsgPack, CContentRoot * contentRoot, bool & fHandled) Line 1411	C++
[Inline Frame] Windows.UI.Xaml.dll!CJupiterControl::HandlePointerMessage(unsigned int) Line 754	C++
Windows.UI.Xaml.dll!CJupiterWindow::OnIslandPointerMessage(unsigned int uMsg, CContentRoot * contentRoot, Windows::UI::Core::IPointerEventArgs * args) Line 2375	C++
Windows.UI.Xaml.dll!CXamlIslandRoot::InjectPointerMessage(unsigned int msg, Windows::UI::Core::IPointerEventArgs * args) Line 611	C++
[Inline Frame] Windows.UI.Xaml.dll!CXamlIslandRoot::OnIslandPointerReleased(IInspectable *) Line 551	C++
[Inline Frame] Windows.UI.Xaml.dll!CXamlIslandRoot::SubscribeToPointerInputObserverEvents::__l43::<lambda_85fbf4d407c7e311044a83519608d385>::operator()(Windows::UI::Internal::Input::IPointerInputObserver *) Line 1447	C++
Windows.UI.Xaml.dll!Microsoft::WRL::Details::DelegateArgTraits<long (__cdecl Windows::Foundation::ITypedEventHandler_impl<Windows::Foundation::Internal::AggregateType<Windows::UI::Internal::Input::PointerInputObserver *,Windows::UI::Internal::Input::IPointerInputObserver *>,Windows::Foundation::Internal::AggregateType<Windows::UI::Core::PointerEventArgs *,Windows::UI::Core::IPointerEventArgs *>>::*)(Windows::UI::Internal::Input::IPointerInputObserver *,Windows::UI::Core::IPointerEventArgs *)>::DelegateInvokeHelper<Microsoft::WRL::Implements<Microsoft::WRL::RuntimeClassFlags<2>,Windows::Foundation::ITypedEventHandler<Windows::UI::Internal::Input::PointerInputObserver *,Windows::UI::Core::PointerEventArgs *>,Microsoft::WRL::FtmBase>,<lambda_85fbf4d407c7e311044a83519608d385> &,-1,Windows::UI::Internal::Input::IPointerInputObserver *,Windows::UI::Core::IPointerEventArgs *>::Invoke(Windows::UI::Internal::Input::IPointerInputObserver * <args_0>, Windows::UI::Core::IPointerEventArgs * <args_1>) Line 354	C++
InputHost.dll!Microsoft::WRL::Details::DelegateArgTraits<long ( Windows::Foundation::ITypedEventHandler_impl<struct Windows::Foundation::Internal::AggregateType<class Windows::UI::Internal::Input::PointerInputObserver *,struct Windows::UI::Internal::Input::IPointerInputObserver *>,struct Windows::Foundation::Internal::AggregateType<class Windows::UI::Core::PointerEventArgs *,struct Windows::UI::Core::IPointerEventArgs *> >::*)(struct Windows::UI::Internal::Input::IPointerInputObserver *,struct Windows::UI::Core::IPointerEventArgs *)>::DelegateInvokeHelper<struct Microsoft::WRL::Implements<struct Microsoft::WRL::RuntimeClassFlags<2>,struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *>,class Microsoft::WRL::FtmBase>,class <lambda_b5222d234632a4a0be2fddaf062a0811>,-1,struct Windows::UI::Internal::Input::IPointerInputObserver *,struct Windows::UI::Core::IPointerEventArgs *>::Invoke(struct Windows::UI::Internal::Input::IPointerInp	Unknown
InputHost.dll!Microsoft::WRL::InvokeTraits<-2>::InvokeDelegates<class <lambda_f45aed66b5921ed661cdce4c5fc41fcc>,struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *> >(class <lambda_f45aed66b5921ed661cdce4c5fc41fcc>,class Microsoft::WRL::Details::EventTargetArray *,class Microsoft::WRL::EventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> > *)	Unknown
InputHost.dll!Microsoft::WRL::EventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> >::DoInvoke<class <lambda_f45aed66b5921ed661cdce4c5fc41fcc> >(class <lambda_f45aed66b5921ed661cdce4c5fc41fcc>)	Unknown
InputHost.dll!Microsoft::WRL2::ContextSession::LeaveSession_Callback<class <lambda_e3ec73fb4105148165e124077d5d30c6> >(class <lambda_e3ec73fb4105148165e124077d5d30c6>)	Unknown
InputHost.dll!PointerInputObserverWinRT::InvokeEventHelper_Callback<class Microsoft::WRL2::ContextEventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> >,class PointerInputObserverWinRT::Api>(struct WindowsMessageDeliveryMessage *,class Microsoft::WRL2::ContextEventSource<struct Windows::Foundation::ITypedEventHandler<class Windows::UI::Internal::Input::PointerInputObserver *,class Windows::UI::Core::PointerEventArgs *>,struct Microsoft::WRL::InvokeModeOptions<-2> > &,class PointerInputObserverWinRT::Api *)	Unknown
InputHost.dll!PointerInputObserverWinRT::OnWindowMessage_Callback(struct WindowsMessageDeliveryMessage *)	Unknown
InputHost.dll!std::_Func_impl_no_alloc<<lambda_7ff7aeac0ddd8d862d847848f86f2968>,enum InputSiteIterationDecision,Microsoft::WRL::ComPtr<BaseInputObjectWinRT> const &>::_Do_call()	Unknown
InputHost.dll!SystemIslandInputSiteWinRT::OnWindowMessage_Callback(struct WindowsMessageDeliveryMessage *)	Unknown
InputHost.dll!WindowsMessageDeliveryInputSiteWinRT::Private::OnWindowMessage_Callback(struct WindowsMessageDeliveryMessage *)	Unknown
InputHost.dll!WindowsMessageDeliveryAdapter::ProcessWindowMessage_NoLock_Callback(struct HWND__ *,unsigned int,unsigned __int64,__int64)	Unknown
user32.dll!UserCallWinProcCheckWow(struct _ACTIVATION_CONTEXT *,__int64 (*)(struct tagWND *,unsigned int,unsigned __int64,__int64),struct HWND__ *,enum _WM_VALUE,unsigned __int64,__int64,void *,int)	Unknown
user32.dll!DispatchMessageWorker()	Unknown
WindowsTerminal.exe!WindowEmperor::HandleCommandlineArgs(int nCmdShow) Line 388	C++
WindowsTerminal.exe!wWinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, wchar_t * __formal, int nCmdShow) Line 125	C++
[Inline Frame] WindowsTerminal.exe!invoke_main() Line 118	C++
WindowsTerminal.exe!__scrt_common_main_seh() Line 288	C++
kernel32.dll!BaseThreadInitThunk()	Unknown
ntdll.dll!RtlUserThreadStart()	Unknown
```


Discussion / hints:
(from triage) This might not be a WinUI issue since we handle a lot of object lifetime maintenance. We should investigate.
This issue is triggered by #15924 which causes a tab to be closed twice (the tab bar is still in the process of closing the previous tab). For all intents and purposes, I consider this a WinUI bug which doesn't synchronously update the tabs array (very unexpected, I know) but this allows us to work around it.

Fix patch:
diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp
index 8407296e296..60e2e94bc2f 100644
--- a/src/cascadia/TerminalApp/TabBase.cpp
+++ b/src/cascadia/TerminalApp/TabBase.cpp
@@ -32,6 +32,7 @@ namespace winrt::TerminalApp::implementation
     {
         ASSERT_UI_THREAD();
 
+        // NOTE: `TerminalPage::_HandleCloseTabRequested` relies on the content being null after this call.
         Content(nullptr);
     }
 
diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp
index cf0df031750..3b2691df936 100644
--- a/src/cascadia/TerminalApp/TabManagement.cpp
+++ b/src/cascadia/TerminalApp/TabManagement.cpp
@@ -158,6 +158,8 @@ namespace winrt::TerminalApp::implementation
         // Set this tab's icon to the icon from the content
         _UpdateTabIcon(*newTabImpl);
 
+        // This is necessary, because WinUI does not have support for middle clicks.
+        // Its Tapped event doesn't provide the information what button was used either.
         tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabPointerPressed });
         tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabPointerReleased });
         tabViewItem.PointerExited({ this, &TerminalPage::_OnTabPointerExited });
@@ -903,19 +905,39 @@ namespace winrt::TerminalApp::implementation
         if (_tabPointerMiddleButtonPressed && !eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
         {
             _tabPointerMiddleButtonPressed = false;
-            if (const auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
+            if (auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
             {
                 tabViewItem.ReleasePointerCapture(eventArgs.Pointer());
-                auto tab = _GetTabByTabViewItem(tabViewItem);
-                if (!_tabPointerMiddleButtonExited && tab)
+                if (!_tabPointerMiddleButtonExited)
                 {
-                    _HandleCloseTabRequested(tab);
+                    _OnTabPointerReleasedCloseTab(std::move(tabViewItem));
                 }
             }
             eventArgs.Handled(true);
         }
     }
 
+    safe_void_coroutine TerminalPage::_OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender)
+    {
+        const auto tab = _GetTabByTabViewItem(sender);
+        if (!tab)
+        {
+            co_return;
+        }
+
+        // WinUI asynchronously updates its tab view items, so it may happen that we're given a
+        // `TabViewItem` that still contains a `TabBase` which has actually already been removed.
+        // First we must yield once, to flush out whatever TabView is currently doing.
+        const auto strong = get_strong();
+        co_await wil::resume_foreground(Dispatcher());
+
+        // `tab.Shutdown()` in `_RemoveTab()` sets the content to null = This checks if the tab is closed.
+        if (tab.Content())
+        {
+            _HandleCloseTabRequested(tab);
+        }
+    }
+
     // Method Description:
     // - Tracking pointer state for tab remove
     // Arguments:
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index 368cb77cd8d..d094e07f0fe 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -433,6 +433,7 @@ namespace winrt::TerminalApp::implementation
         bool _tabPointerMiddleButtonExited{ false };
         void _OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
         void _OnTabPointerReleased(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
+        safe_void_coroutine _OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender);
         void _OnTabPointerEntered(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
         void _OnTabPointerExited(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
 
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index ca50edefe09..c19730b597c 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -851,6 +851,9 @@ namespace winrt::TerminalApp::implementation
     {
         ASSERT_UI_THREAD();
 
+        // Don't forget to call the overridden function. :)
+        TabBase::Shutdown();
+
         if (_rootPane)
         {
             _rootPane->Shutdown();


Test patch: