diff --git a/src/windows/service/exe/LxssUserSession.cpp b/src/windows/service/exe/LxssUserSession.cpp index 5b31400193..36e5d13e82 100644 --- a/src/windows/service/exe/LxssUserSession.cpp +++ b/src/windows/service/exe/LxssUserSession.cpp @@ -1066,6 +1066,7 @@ HRESULT LxssUserSessionImpl::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _In RETURN_HR_IF(E_INVALIDARG, (WI_IsAnyFlagSet(Flags, ~LXSS_EXPORT_DISTRO_FLAGS_ALL))); LXSS_DISTRO_CONFIGURATION configuration; + std::vector pidTerminations; wil::unique_hkey distroKey; try { @@ -1086,12 +1087,13 @@ HRESULT LxssUserSessionImpl::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _In RETURN_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, WI_IsFlagClear(configuration.Flags, LXSS_DISTRO_FLAGS_VM_MODE) && !g_lxcoreInitialized); // Add the distribution to the list of converting distributions. - _ConversionBegin(configuration.DistroId, LxssDistributionStateExporting); + pidTerminations = _ConversionBegin(configuration.DistroId, LxssDistributionStateExporting); } CATCH_RETURN() // Set up a scope exit member to remove the distribution from the converting list. auto exportComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); }); + RETURN_IF_FAILED(_WaitForPidTerminations(pidTerminations)); // Log telemetry to track how long exporting the distribution takes. WSL_LOG_TELEMETRY( @@ -1472,6 +1474,7 @@ HRESULT LxssUserSessionImpl::RegisterDistribution( DistributionRegistration registration; LXSS_DISTRO_CONFIGURATION configuration; + std::vector pidTerminations; std::filesystem::path distributionPath; auto config = _GetResultantConfig(userToken.get()); @@ -1537,11 +1540,12 @@ HRESULT LxssUserSessionImpl::RegisterDistribution( configuration = s_GetDistributionConfiguration(registration, DistributionName == nullptr); // Add the distribution to the list of converting distributions. - _ConversionBegin(configuration.DistroId, LxssDistributionStateInstalling); + pidTerminations = _ConversionBegin(configuration.DistroId, LxssDistributionStateInstalling); } // Set up a scope exit member to remove the distribution from the converting list. auto installComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); }); + THROW_IF_FAILED(_WaitForPidTerminations(pidTerminations)); // Declare a scope exit variable to clean up on failure. ULONG deleteFlags = 0; @@ -1872,10 +1876,11 @@ try auto runAsUser = wil::CoImpersonateClient(); std::filesystem::path vhdPath; LXSS_DISTRO_CONFIGURATION configuration{}; + std::vector pidTerminations; + const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); { std::lock_guard lock(m_instanceLock); - const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); const wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get()); const auto registration = DistributionRegistration::Open(lxssKey.get(), *DistroGuid); configuration = s_GetDistributionConfiguration(registration); @@ -1887,8 +1892,14 @@ try THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), wsl::shared::Localization::MessageCompactVhdNotSupported()); } - _ConversionBegin(configuration.DistroId, LxssDistributionStateCompacting); + pidTerminations = _ConversionBegin(configuration.DistroId, LxssDistributionStateCompacting); + } + + auto compactionComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); }); + THROW_IF_FAILED(_WaitForPidTerminations(pidTerminations)); + { + std::lock_guard lock(m_instanceLock); // Trim the filesystem before compaction so the host can reclaim the freed blocks. // // WSL2 does not mount ext4 with 'discard' and does not run fsck at boot, so blocks freed @@ -1908,8 +1919,6 @@ try CATCH_LOG(); } - auto compactionComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); }); - THROW_IF_FAILED_MSG( wil::ResultFromException([&] { wsl::core::filesystem::CompactVhd(vhdPath.c_str()); }), "Failed to compact VHD: %ls", @@ -1924,6 +1933,7 @@ HRESULT LxssUserSessionImpl::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Vers DistributionRegistration registration; LXSS_DISTRO_CONFIGURATION configuration; + std::vector pidTerminations; const auto userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation); wil::unique_hkey lxssKey = s_OpenLxssUserKey(userToken.get()); try @@ -1953,7 +1963,7 @@ HRESULT LxssUserSessionImpl::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Vers RETURN_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !g_lxcoreInitialized); // Add the distribution to the list of converting distributions. - _ConversionBegin(configuration.DistroId, LxssDistributionStateConverting); + pidTerminations = _ConversionBegin(configuration.DistroId, LxssDistributionStateConverting); // Remove the distribution ID from m_updatedInitDistros so init is updated on the next launch (in the case of a conversion to WSL1). m_updatedInitDistros.erase( @@ -1963,6 +1973,7 @@ HRESULT LxssUserSessionImpl::SetVersion(_In_ LPCGUID DistroGuid, _In_ ULONG Vers // Set up a scope exit member to remove the distribution from the converting list. auto conversionComplete = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { _ConversionComplete(configuration.DistroId); }); + RETURN_IF_FAILED(_WaitForPidTerminations(pidTerminations)); // Log telemetry to track how long enabling VM mode takes. WSL_LOG_TELEMETRY( @@ -2513,11 +2524,22 @@ HRESULT LxssUserSessionImpl::UnregisterDistribution(_In_ LPCGUID DistroGuid) } _Requires_lock_held_(m_instanceLock) -void LxssUserSessionImpl::_ConversionBegin(_In_ GUID DistroGuid, _In_ LxssDistributionState State) +std::vector LxssUserSessionImpl::_ConversionBegin(_In_ GUID DistroGuid, _In_ LxssDistributionState State) { _EnsureNotLocked(&DistroGuid); _TerminateInstanceInternal(&DistroGuid); + + std::vector pidTerminations; + for (const auto& termination : m_pidTerminations) + { + if (IsEqualGUID(termination.second.DistroId, DistroGuid)) + { + pidTerminations.push_back(termination.second); + } + } + m_lockedDistributions.emplace_back(DistroGuid, State); + return pidTerminations; } _Requires_lock_not_held_(m_instanceLock) @@ -2529,6 +2551,31 @@ void LxssUserSessionImpl::_ConversionComplete(_In_ GUID DistroGuid) _VmCheckIdle(); } +_Requires_lock_not_held_(m_instanceLock) +HRESULT LxssUserSessionImpl::_WaitForPidTerminations(_In_ const std::vector& PidTerminations) +try +{ + SlowOperationWatcher slowOperation{"WaitForPidTermination"}; + const auto startTime = GetTickCount64(); + for (const auto& termination : PidTerminations) + { + const auto elapsed = GetTickCount64() - startTime; + const auto timeout = termination.Timeout == INFINITE ? INFINITE + : elapsed >= termination.Timeout ? 0 + : termination.Timeout - gsl::narrow_cast(elapsed); + if (!termination.Event.wait(timeout)) + { + WSL_LOG( + "PidTerminationTimeout", + TraceLoggingValue(termination.ClientId, "pid"), + TraceLoggingValue(termination.Timeout, "timeout")); + } + } + + return S_OK; +} +CATCH_RETURN() + _Requires_exclusive_lock_held_(m_instanceLock) void LxssUserSessionImpl::_CreateLegacyRegistration(_In_ HKEY LxssKey, _In_ HANDLE UserToken) { @@ -3038,6 +3085,12 @@ void LxssUserSessionImpl::_CreateVm() auto unlock = wil::scope_exit([&]() { m_instanceLock.unlock(); }); TerminateByClientIdLockHeld(Pid); + + if (const auto termination = m_pidTerminations.find(Pid); termination != m_pidTerminations.end()) + { + termination->second.Event.SetEvent(); + m_pidTerminations.erase(termination); + } }; // N.B. The callbacks must be registered outside of the above try/catch. @@ -3736,6 +3789,13 @@ bool LxssUserSessionImpl::_TerminateInstanceInternal(_In_ LPCGUID DistroGuid, _I { if (const auto* wslcoreInstance = dynamic_cast(instance->second.get()); wslcoreInstance != nullptr) { + // Stop releases the system distro, so capture its PID while it is still available. + PidTermination termination; + termination.DistroId = *DistroGuid; + termination.ClientId = wslcoreInstance->GetClientId(); + termination.Timeout = gsl::narrow_cast(m_utilityVm->GetConfig().DistributionStartTimeout); + m_pidTerminations.try_emplace(termination.ClientId, std::move(termination)); + m_pluginManager.OnDistributionStopping(&m_session, wslcoreInstance->DistributionInformation()); } @@ -4038,6 +4098,13 @@ void LxssUserSessionImpl::_VmTerminate() m_utilityVm.reset(); m_vmId.store(GUID_NULL); + // VM teardown cancels exit callbacks, so complete any remaining waiters after the VM has released its resources. + for (const auto& termination : m_pidTerminations) + { + termination.second.Event.SetEvent(); + } + m_pidTerminations.clear(); + // Reset the user's token since its lifetime is tied to the VM. m_userToken.reset(); m_session.UserToken = nullptr; diff --git a/src/windows/service/exe/LxssUserSession.h b/src/windows/service/exe/LxssUserSession.h index 67908c4a6e..4466ba6a8b 100644 --- a/src/windows/service/exe/LxssUserSession.h +++ b/src/windows/service/exe/LxssUserSession.h @@ -544,11 +544,19 @@ class LxssUserSessionImpl : public std::enable_shared_from_this /// Adds a distro to the list of converting distros. /// _Requires_lock_held_(m_instanceLock) - void _ConversionBegin(_In_ GUID DistroGuid, _In_ LxssDistributionState State); + std::vector _ConversionBegin(_In_ GUID DistroGuid, _In_ LxssDistributionState State); /// /// Removes a distro from the list of converting distros and checks if the @@ -557,6 +565,9 @@ class LxssUserSessionImpl : public std::enable_shared_from_this& PidTerminations); + /// /// Creates a distribution registration for legacy installs. /// @@ -822,6 +833,11 @@ class LxssUserSessionImpl : public std::enable_shared_from_this _Guarded_by_(m_instanceLock) std::list> m_lockedDistributions; + /// + /// Contains pending WSL2 exits until their exit notification or VM teardown, even if a conversion wait times out. + /// + _Guarded_by_(m_instanceLock) std::map m_pidTerminations; + /// /// The running utility vm for WSL2 distributions. ///