Fix force shutdown deadlock race (#41269)

During force shutdown, the shutdown is called before the WslCoreVm destructor clears the m_onExit callback. The shutdown operation will trigger the HCS OnExit callback. If that callback is triggered before the m_onExit callback is cleared, the m_onExit callback still gets called. Then it waits for the m_instanceLock which is already locked by the shutdown call. Meanwhile the shutdown call will wait for the HCS OnExit callback to return in _VmTerminate via the HcsCloseComputeSystem call. Which creates a deadlock and blocks the wsl service. This PR adds an atomic flag to disable the caller provided callback.

Feng Wang committed Aug 10, 2026 at 10:04 UTC f2f0667e4b75dd6c33f89773963a3806587a3685
2 files changed +15
src/windows/service/exe/LxssUserSession.cpp
+10
@@ -2152,12 +2152,16 @@ HRESULT LxssUserSessionImpl::Shutdown(_In_ bool PreventNewInstances, ShutdownBeh
2152 {
2153 try
2154 {
2155 + auto resetVmTerminationCallback = wil::scope_exit([&]() { m_suppressVmTerminationCallback.store(false); });
2156 +
2157 auto forceTerminate = [this]() {
2158 auto vmId = m_vmId.load();
2159 if (!IsEqualGUID(vmId, GUID_NULL))
2160 {
2161 auto vmIdStr = wsl::shared::string::GuidToString<wchar_t>(vmId, wsl::shared::string::GuidToStringFlags::Uppercase);
2162
2163 + m_suppressVmTerminationCallback.store(true);
2164 +
2165 auto result = wil::ResultFromException([&]() {
2166 auto computeSystem = wsl::windows::common::hcs::OpenComputeSystem(vmIdStr.c_str(), GENERIC_ALL);
2167 wsl::windows::common::hcs::TerminateComputeSystem(computeSystem.get());
@@ -2209,6 +2213,7 @@ HRESULT LxssUserSessionImpl::Shutdown(_In_ bool PreventNewInstances, ShutdownBeh
2213
2214 // Terminate the utility VM.
2215 _VmTerminate();
2216 + resetVmTerminationCallback.reset();
2217
2218 // Reset the proxy state.
2219 // We don't clear it in _VMTerminate because we want to cache results if possible.
@@ -4236,6 +4241,11 @@ try
4241 {
4242 UNREFERENCED_PARAMETER(VmId);
4243
4244 + if (UserSession->m_suppressVmTerminationCallback.load())
4245 + {
4246 + return;
4247 + }
4248 +
4249 UserSession->TerminateByClientId(LXSS_CLIENT_ID_WILDCARD);
4250 return;
4251 }
src/windows/service/exe/LxssUserSession.h
+5
@@ -816,6 +816,11 @@ private:
816
817 std::atomic<GUID> m_vmId{GUID_NULL};
818
819 + /// <summary>
820 + /// True when the VM termination callback should not perform session cleanup.
821 + /// </summary>
822 + std::atomic<bool> m_suppressVmTerminationCallback{false};
823 +
824 /// <summary>
825 /// Contains the user sid for the session.
826 /// </summary>