Repository: microsoft/terminal (language: cpp)

Issue:
Windows Terminal deadlock/lockleak after startup.
### Windows Terminal version

1.23.10353

### Windows build number

27788

### Steps to reproduce

My computer pops up a Windows Terminal window upon startup, which becomes unresponsive and freezes. This has happened twice now, with the previous occurrence happening a few days ago. I attached to the process in VS, paused the program to check where it was blocked, and found that a lock is waiting and has never been notified within TerminalConnection.dll.

![Image](https://github.com/user-attachments/assets/917f088a-0b91-428f-85b2-0be6af74bd93)

![Image](https://github.com/user-attachments/assets/4725d729-81ca-4446-9f94-69b5bc4416b0)

![Image](https://github.com/user-attachments/assets/ac67bc18-69fc-442c-add6-5ac41c062564)



Discussion / hints:
Some suggestions on solving this,
1. Update your windows.
2. Go to the call stack and analyze, it maybe you are having shared resources that multiple threads are trying to access simultaneously.
3. Try repairing windows or re-setting the windows 
I can confirm this is also happening for me on Windows 11 24H2, on version 1.21.10351.0 - with the exact same call stack according to x64dbg. It also happened to unfreeze itself after I closed a game that was open, possibly related to #18040.

Fix patch:
diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp
index a6edb232430..01b25c9ffe4 100644
--- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp
+++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp
@@ -14,6 +14,13 @@ static DWORD g_cTerminalHandoffRegistration = 0;
 // Mutex so we only do start/stop/establish one at a time.
 static std::shared_mutex _mtx;
 
+// This is the callback that will be called when a connection is received.
+// Call this once during startup and don't ever change it again (race condition).
+void CTerminalHandoff::s_setCallback(NewHandoffFunction callback) noexcept
+{
+    _pfnHandoff = callback;
+}
+
 // Routine Description:
 // - Starts listening for TerminalHandoff requests by registering
 //   our class and interface with COM.
@@ -21,24 +28,19 @@ static std::shared_mutex _mtx;
 // - pfnHandoff - Function to callback when a handoff is received
 // Return Value:
 // - S_OK, E_NOT_VALID_STATE (start called when already started) or relevant COM registration error.
-HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff)
+HRESULT CTerminalHandoff::s_StartListening()
 try
 {
     std::unique_lock lock{ _mtx };
 
-    RETURN_HR_IF(E_NOT_VALID_STATE, _pfnHandoff != nullptr);
-
     const auto classFactory = Make<SimpleClassFactory<CTerminalHandoff>>();
-
-    RETURN_IF_NULL_ALLOC(classFactory);
+    RETURN_LAST_ERROR_IF_NULL(classFactory);
 
     ComPtr<IUnknown> unk;
     RETURN_IF_FAILED(classFactory.As(&unk));
 
     RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration));
 
-    _pfnHandoff = pfnHandoff;
-
     return S_OK;
 }
 CATCH_RETURN()
@@ -53,15 +55,6 @@ CATCH_RETURN()
 HRESULT CTerminalHandoff::s_StopListening()
 {
     std::unique_lock lock{ _mtx };
-    return s_StopListeningLocked();
-}
-
-// See s_StopListening()
-HRESULT CTerminalHandoff::s_StopListeningLocked()
-{
-    RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff);
-
-    _pfnHandoff = nullptr;
 
     if (g_cTerminalHandoffRegistration)
     {
@@ -92,22 +85,15 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE si
 {
     try
     {
-        std::unique_lock lock{ _mtx };
-
-        // s_StopListeningLocked sets _pfnHandoff to nullptr.
-        // localPfnHandoff is tested for nullness below.
-#pragma warning(suppress : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23).
-        auto localPfnHandoff = _pfnHandoff;
-
         // Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call.
         // COM does not automatically clean that up for us. We must do it.
-        LOG_IF_FAILED(s_StopListeningLocked());
+        LOG_IF_FAILED(s_StopListening());
 
         // Report an error if no one registered a handoff function before calling this.
-        THROW_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff);
+        THROW_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff);
 
         // Call registered handler from when we started listening.
-        THROW_IF_FAILED(localPfnHandoff(in, out, signal, reference, server, client, startupInfo));
+        THROW_IF_FAILED(_pfnHandoff(in, out, signal, reference, server, client, startupInfo));
 
 #pragma warning(suppress : 26477)
         TraceLoggingWrite(
diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.h b/src/cascadia/TerminalConnection/CTerminalHandoff.h
index 440a2636f20..004b3f52745 100644
--- a/src/cascadia/TerminalConnection/CTerminalHandoff.h
+++ b/src/cascadia/TerminalConnection/CTerminalHandoff.h
@@ -38,11 +38,11 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff))
 
 #pragma endregion
 
-    static HRESULT s_StartListening(NewHandoffFunction pfnHandoff);
-    static HRESULT s_StopListening();
+    static void s_setCallback(NewHandoffFunction callback) noexcept;
+    static HRESULT s_StartListening();
 
 private:
-    static HRESULT s_StopListeningLocked();
+    static HRESULT s_StopListening();
 };
 
 // Disable warnings from the CoCreatableClass macro as the value it provides for
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp
index 0a0b26aa934..95517373c72 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.cpp
+++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp
@@ -780,12 +780,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
 
     void ConptyConnection::StartInboundListener()
     {
-        THROW_IF_FAILED(CTerminalHandoff::s_StartListening(&ConptyConnection::NewHandoff));
-    }
+        static const auto init = []() noexcept {
+            CTerminalHandoff::s_setCallback(&ConptyConnection::NewHandoff);
+            return true;
+        }();
 
-    void ConptyConnection::StopInboundListener()
-    {
-        THROW_IF_FAILED(CTerminalHandoff::s_StopListening());
+        CTerminalHandoff::s_StartListening();
     }
 
     // Function Description:
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h
index ca67588f646..69edd7b7a8e 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.h
+++ b/src/cascadia/TerminalConnection/ConptyConnection.h
@@ -36,7 +36,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
         WORD ShowWindow() const noexcept;
 
         static void StartInboundListener();
-        static void StopInboundListener();
 
         static winrt::event_token NewConnection(const NewConnectionHandler& handler);
         static void NewConnection(const winrt::event_token& token);
diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl
index 09466b9d11e..e1ed83cc0c0 100644
--- a/src/cascadia/TerminalConnection/ConptyConnection.idl
+++ b/src/cascadia/TerminalConnection/ConptyConnection.idl
@@ -23,7 +23,6 @@ namespace Microsoft.Terminal.TerminalConnection
 
         static event NewConnectionHandler NewConnection;
         static void StartInboundListener();
-        static void StopInboundListener();
 
         static Windows.Foundation.Collections.ValueSet CreateSettings(String cmdline,
                                                                       String startingDirectory,


Test patch: