diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index 88dc3ea171..18b79acead 100644 --- a/msipackage/package.wix.in +++ b/msipackage/package.wix.in @@ -306,14 +306,6 @@ - - - - - - - - diff --git a/src/windows/WslcSDK/CMakeLists.txt b/src/windows/WslcSDK/CMakeLists.txt index 8c026430f6..45b1a0dd25 100644 --- a/src/windows/WslcSDK/CMakeLists.txt +++ b/src/windows/WslcSDK/CMakeLists.txt @@ -1,7 +1,6 @@ set(SOURCES IOCallback.cpp ProgressCallback.cpp - TerminationCallback.cpp CrashDumpCallback.cpp wslcsdk.cpp WslcsdkPrivate.cpp @@ -10,7 +9,6 @@ set(HEADERS Defaults.h IOCallback.h ProgressCallback.h - TerminationCallback.h CrashDumpCallback.h wslcsdk.h WslcsdkPrivate.h diff --git a/src/windows/WslcSDK/TerminationCallback.cpp b/src/windows/WslcSDK/TerminationCallback.cpp deleted file mode 100644 index bd98a59529..0000000000 --- a/src/windows/WslcSDK/TerminationCallback.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/*++ - -Copyright (c) Microsoft. All rights reserved. - -Module Name: - - TerminationCallback.cpp - -Abstract: - - Implementation of a type that implements ITerminationCallback. - ---*/ -#include "precomp.h" -#include "TerminationCallback.h" - -namespace { -WslcSessionTerminationReason ConvertReason(WSLCVirtualMachineTerminationReason Reason) -{ - switch (Reason) - { - case WSLCVirtualMachineTerminationReasonShutdown: - return WSLC_SESSION_TERMINATION_REASON_SHUTDOWN; - case WSLCVirtualMachineTerminationReasonCrashed: - return WSLC_SESSION_TERMINATION_REASON_CRASHED; - default: - return WSLC_SESSION_TERMINATION_REASON_UNKNOWN; - } -} -} // namespace - -TerminationCallback::TerminationCallback(WslcSessionTerminationCallback callback, PVOID context) : - m_callback(callback), m_context(context) -{ -} - -// TODO: Details from the runtime are dropped; should the SDK callback function be updated to include the reasons string? -HRESULT STDMETHODCALLTYPE TerminationCallback::OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR) -{ - if (m_callback) - { - m_callback(ConvertReason(Reason), m_context); - } - - return S_OK; -} - -winrt::com_ptr TerminationCallback::CreateIf(const WslcSessionOptionsInternal* options) -{ - if (options->terminationCallback) - { - return winrt::make_self(options->terminationCallback, options->terminationCallbackContext); - } - else - { - return nullptr; - } -} diff --git a/src/windows/WslcSDK/TerminationCallback.h b/src/windows/WslcSDK/TerminationCallback.h deleted file mode 100644 index afd8542bf3..0000000000 --- a/src/windows/WslcSDK/TerminationCallback.h +++ /dev/null @@ -1,32 +0,0 @@ -/*++ - -Copyright (c) Microsoft. All rights reserved. - -Module Name: - - TerminationCallback.h - -Abstract: - - Header for a type that implements ITerminationCallback. - ---*/ -#pragma once -#include "wslc.h" -#include "wslcsdkprivate.h" -#include - -struct TerminationCallback : public winrt::implements -{ - TerminationCallback(WslcSessionTerminationCallback callback, PVOID context); - - // ITerminationCallback - HRESULT STDMETHODCALLTYPE OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details) override; - - // Creates a TerminationCallback if the options provides a callback. - static winrt::com_ptr CreateIf(const WslcSessionOptionsInternal* options); - -private: - WslcSessionTerminationCallback m_callback = nullptr; - PVOID m_context = nullptr; -}; diff --git a/src/windows/WslcSDK/WslcsdkPrivate.h b/src/windows/WslcSDK/WslcsdkPrivate.h index 5000363abd..d487285665 100644 --- a/src/windows/WslcSDK/WslcsdkPrivate.h +++ b/src/windows/WslcSDK/WslcsdkPrivate.h @@ -33,8 +33,6 @@ typedef struct WslcSessionOptionsInternal WslcVhdRequirements vhdRequirements; WslcSessionFeatureFlags featureFlags; - WslcSessionTerminationCallback terminationCallback; - PVOID terminationCallbackContext; } WslcSessionOptionsInternal; static_assert(sizeof(WslcSessionOptionsInternal) == WSLC_SESSION_OPTIONS_SIZE, "WSLC_SESSION_OPTIONS_INTERNAL size mismatch"); @@ -107,7 +105,6 @@ const WslcContainerOptionsInternal* GetInternalType(const WslcContainerSettings* struct WslcSessionImpl { wil::com_ptr session; - wil::com_ptr terminationCallback; }; WslcSessionImpl* GetInternalType(WslcSession handle); diff --git a/src/windows/WslcSDK/winrt/Session.cpp b/src/windows/WslcSDK/winrt/Session.cpp index 5a29437c99..46bb7afdcc 100644 --- a/src/windows/WslcSDK/winrt/Session.cpp +++ b/src/windows/WslcSDK/winrt/Session.cpp @@ -52,12 +52,16 @@ void Session::Start() throw winrt::hresult_illegal_method_call(L"Session has already been started"); } - winrt::check_hresult(WslcSetSessionSettingsTerminationCallback(GetStructPointer(m_settings), TerminatedCallback, /* context */ this)); - wil::unique_cotaskmem_string errorMessage; auto hr = WslcCreateSession(GetStructPointer(m_settings), m_session.put(), errorMessage.put()); THROW_MSG_IF_FAILED(hr, errorMessage); m_settings = nullptr; + + winrt::check_hresult(WslcGetSessionTerminationEvent(m_session.get(), m_terminationEvent.put())); + + m_terminationWait.reset(CreateThreadpoolWait(&Session::OnTerminated, this, nullptr)); + THROW_LAST_ERROR_IF_NULL(m_terminationWait); + SetThreadpoolWait(m_terminationWait.get(), m_terminationEvent.get(), nullptr); } void Session::EnsureStarted() const @@ -300,11 +304,15 @@ WslcSession Session::ToHandle() return m_session.get(); } -void CALLBACK Session::TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept +void CALLBACK Session::OnTerminated(PTP_CALLBACK_INSTANCE /* instance */, PVOID context, PTP_WAIT /* wait */, TP_WAIT_RESULT /* waitResult */) noexcept { try { auto session = static_cast(context); + + WslcSessionTerminationReason reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN; + LOG_IF_FAILED(WslcGetSessionTerminationReason(session->m_session.get(), &reason)); + session->m_terminatedEvent(static_cast(reason)); } CATCH_LOG(); diff --git a/src/windows/WslcSDK/winrt/Session.h b/src/windows/WslcSDK/winrt/Session.h index 954591262c..f6e8f7bd14 100644 --- a/src/windows/WslcSDK/winrt/Session.h +++ b/src/windows/WslcSDK/winrt/Session.h @@ -45,12 +45,15 @@ struct Session : SessionT void EnsureStarted() const; winrt::Microsoft::WSL::Containers::SessionSettings m_settings; // Only kept until Start() is called - static void CALLBACK TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept; + // Threadpool callback that raises the Terminated event once the session's termination handle is signaled. + static void CALLBACK OnTerminated(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_WAIT wait, TP_WAIT_RESULT waitResult) noexcept; - // Releasing the session handle may trigger the termination callback. - // Keep these two in this order so that the session handle is released before the termination event is destructed. winrt::event m_terminatedEvent; wil::unique_any m_session{nullptr}; + + // Bridges the one-off termination event surfaced by the SDK to the WinRT Terminated event. + wil::unique_handle m_terminationEvent; + wil::unique_threadpool_wait m_terminationWait; }; } // namespace winrt::Microsoft::WSL::Containers::implementation namespace winrt::Microsoft::WSL::Containers::factory_implementation { diff --git a/src/windows/WslcSDK/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 25dcdf1426..f83ff24fc0 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -17,7 +17,6 @@ Module Name: #include "WslcsdkPrivate.h" #include "Defaults.h" #include "ProgressCallback.h" -#include "TerminationCallback.h" #include "CrashDumpCallback.h" #include "Localization.h" #include "WslInstall.h" @@ -435,12 +434,6 @@ try runtimeSettings.MemoryMb = internalType->memoryMb; runtimeSettings.BootTimeoutMs = internalType->timeoutMS; runtimeSettings.NetworkingMode = WSLCNetworkingModeVirtioProxy; - auto terminationCallback = TerminationCallback::CreateIf(internalType); - if (terminationCallback) - { - result->terminationCallback.attach(terminationCallback.as().detach()); - runtimeSettings.TerminationCallback = terminationCallback.get(); - } runtimeSettings.FeatureFlags = ConvertFlags(internalType->featureFlags); WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsVirtioFs); WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsDnsTunneling); @@ -587,15 +580,39 @@ try } CATCH_RETURN(); -STDAPI WslcSetSessionSettingsTerminationCallback( - _In_ WslcSessionSettings* sessionSettings, _In_opt_ WslcSessionTerminationCallback terminationCallback, _In_opt_ PVOID terminationContext) +STDAPI WslcGetSessionTerminationEvent(_In_ WslcSession session, _Out_ HANDLE* terminationEvent) try { - auto internalType = CheckAndGetInternalType(sessionSettings); - RETURN_HR_IF(E_INVALIDARG, terminationCallback == nullptr && terminationContext != nullptr); + RETURN_HR_IF_NULL(E_POINTER, terminationEvent); + *terminationEvent = nullptr; + + auto internalType = CheckAndGetInternalType(session); + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); + + RETURN_HR(internalType->session->GetTerminationEvent(terminationEvent)); +} +CATCH_RETURN(); + +STDAPI WslcGetSessionTerminationReason(_In_ WslcSession session, _Out_ WslcSessionTerminationReason* reason) +try +{ + static_assert( + WSLC_SESSION_TERMINATION_REASON_UNKNOWN == WSLCVirtualMachineTerminationReasonUnknown && + WSLC_SESSION_TERMINATION_REASON_SHUTDOWN == WSLCVirtualMachineTerminationReasonShutdown && + WSLC_SESSION_TERMINATION_REASON_CRASHED == WSLCVirtualMachineTerminationReasonCrashed, + "Termination reason enum values mismatch."); + + RETURN_HR_IF_NULL(E_POINTER, reason); + *reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN; + + auto internalType = CheckAndGetInternalType(session); + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session); + + WSLCVirtualMachineTerminationReason runtimeReason = WSLCVirtualMachineTerminationReasonUnknown; + wil::unique_cotaskmem_string details; + RETURN_IF_FAILED(internalType->session->GetTerminationReason(&runtimeReason, &details)); - internalType->terminationCallback = terminationCallback; - internalType->terminationCallbackContext = terminationContext; + *reason = static_cast(runtimeReason); return S_OK; } @@ -649,10 +666,7 @@ try { auto internalType = CheckAndGetInternalTypeUniquePointer(session); - // Drop the session before the termination callback, in case session destruction triggers - // the termination callback. internalType->session.reset(); - internalType->terminationCallback.reset(); return S_OK; } diff --git a/src/windows/WslcSDK/wslcsdk.def b/src/windows/WslcSDK/wslcsdk.def index a96b4cf008..cc68fd6b1d 100644 --- a/src/windows/WslcSDK/wslcsdk.def +++ b/src/windows/WslcSDK/wslcsdk.def @@ -16,7 +16,8 @@ WslcReleaseContainer WslcReleaseProcess WslcSetSessionSettingsFeatureFlags -WslcSetSessionSettingsTerminationCallback +WslcGetSessionTerminationEvent +WslcGetSessionTerminationReason WslcSetSessionSettingsCpuCount WslcSetSessionSettingsMemory WslcSetSessionSettingsTimeout diff --git a/src/windows/WslcSDK/wslcsdk.h b/src/windows/WslcSDK/wslcsdk.h index 59ce6b4bf7..44db9c8c98 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -43,7 +43,7 @@ EXTERN_C_START #define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */ // Session values -#define WSLC_SESSION_OPTIONS_SIZE 88 +#define WSLC_SESSION_OPTIONS_SIZE 72 #define WSLC_SESSION_OPTIONS_ALIGNMENT 8 typedef struct WslcSessionSettings @@ -124,8 +124,6 @@ typedef enum WslcSessionTerminationReason WSLC_SESSION_TERMINATION_REASON_CRASHED = 2, } WslcSessionTerminationReason; -typedef __callback void(CALLBACK* WslcSessionTerminationCallback)(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context); - typedef struct WslcSessionCrashDumpInfo { _Field_z_ PCWSTR dumpPath; @@ -154,9 +152,8 @@ STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_ STDAPI WslcSetSessionSettingsFeatureFlags(_In_ WslcSessionSettings* sessionSettings, _In_ WslcSessionFeatureFlags flags); -// Pass in Null for callback to clear the termination callback -STDAPI WslcSetSessionSettingsTerminationCallback( - _In_ WslcSessionSettings* sessionSettings, _In_opt_ WslcSessionTerminationCallback terminationCallback, _In_opt_ PVOID terminationContext); +STDAPI WslcGetSessionTerminationEvent(_In_ WslcSession session, _Out_ HANDLE* terminationEvent); +STDAPI WslcGetSessionTerminationReason(_In_ WslcSession session, _Out_ WslcSessionTerminationReason* reason); STDAPI WslcTerminateSession(_In_ WslcSession session); STDAPI WslcReleaseSession(_In_ WslcSession session); diff --git a/src/windows/service/exe/HcsVirtualMachine.cpp b/src/windows/service/exe/HcsVirtualMachine.cpp index 74f4e0d890..0b8bc6a04b 100644 --- a/src/windows/service/exe/HcsVirtualMachine.cpp +++ b/src/windows/service/exe/HcsVirtualMachine.cpp @@ -285,12 +285,6 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings) m_guestDeviceManager = std::make_shared<::GuestDeviceManager>(m_vmIdString, m_vmId); } - // Configure termination callback - if (Settings->TerminationCallback) - { - m_terminationCallback = Settings->TerminationCallback; - } - hcs::RegisterCallback(m_computeSystem.get(), &HcsVirtualMachine::OnVmExitCallback, this); // Create a listening socket for mini_init to connect to once the VM is running. @@ -692,8 +686,6 @@ CATCH_LOG() void HcsVirtualMachine::OnExit(const HCS_EVENT* Event) { - m_vmExitEvent.SetEvent(); - const auto exitStatus = wsl::shared::FromJson(Event->EventData); auto reason = WSLCVirtualMachineTerminationReasonUnknown; @@ -715,12 +707,34 @@ void HcsVirtualMachine::OnExit(const HCS_EVENT* Event) } } - if (m_terminationCallback) - { - LOG_IF_FAILED(m_terminationCallback->OnTermination(reason, Event->EventData)); - } + // Cache the termination reason and details before signaling the exit event. These fields are + // written once here (OnExit fires once and m_vmExitEvent is never reset) and published to readers + // by the SetEvent below; GetTerminationReason only reads them after observing the signaled event. + m_terminationReason = reason; + m_terminationDetails = Event->EventData; + + m_vmExitEvent.SetEvent(); } +HRESULT HcsVirtualMachine::GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) +try +{ + RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr); + + *Reason = WSLCVirtualMachineTerminationReasonUnknown; + *Details = nullptr; + + // m_terminationReason/m_terminationDetails are written once in OnExit before m_vmExitEvent is + // signaled and never modified afterward, so observing the signaled event safely publishes them. + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vmExitEvent.is_signaled()); + + *Reason = m_terminationReason; + *Details = wil::make_cotaskmem_string(m_terminationDetails.c_str()).release(); + + return S_OK; +} +CATCH_RETURN() + void HcsVirtualMachine::OnCrash(const HCS_EVENT* Event) { if (m_crashLogCaptured.load() && m_vmSavedStateCaptured.load()) @@ -863,7 +877,6 @@ WSLCVirtualMachineFactory::WSLCVirtualMachineFactory(_In_ const WSLCSessionSetti m_dmesgOutput.reset(wslutil::DuplicateHandle(wslutil::FromCOMInputHandle(Settings->DmesgOutput), GENERIC_WRITE | SYNCHRONIZE)); } - m_terminationCallback = Settings->TerminationCallback; m_maximumStorageSizeMb = Settings->MaximumStorageSizeMb; m_cpuCount = Settings->CpuCount; m_memoryMb = Settings->MemoryMb; @@ -883,7 +896,6 @@ WSLCSessionSettings WSLCVirtualMachineFactory::BuildSettings() settings.MemoryMb = m_memoryMb; settings.BootTimeoutMs = m_bootTimeoutMs; settings.NetworkingMode = m_networkingMode; - settings.TerminationCallback = m_terminationCallback.get(); settings.FeatureFlags = m_featureFlags; settings.StorageFlags = m_storageFlags; settings.RootVhdOverride = m_rootVhdOverride ? m_rootVhdOverride->c_str() : nullptr; diff --git a/src/windows/service/exe/HcsVirtualMachine.h b/src/windows/service/exe/HcsVirtualMachine.h index 07bf504eed..b4bfc17739 100644 --- a/src/windows/service/exe/HcsVirtualMachine.h +++ b/src/windows/service/exe/HcsVirtualMachine.h @@ -48,6 +48,7 @@ class HcsVirtualMachine IFACEMETHOD(RemoveShare)(_In_ REFGUID ShareId) override; IFACEMETHOD(ApplyGuestCapabilities)(_In_ const WSLCGuestCapabilities* Capabilities) override; IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override; + IFACEMETHOD(GetTerminationReason)(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) override; private: struct DiskInfo @@ -103,7 +104,13 @@ class HcsVirtualMachine std::atomic m_vmSavedStateCaptured = false; std::atomic m_crashLogCaptured = false; - wil::com_ptr m_terminationCallback; + // Termination reason and details, cached in OnExit before m_vmExitEvent is signaled and never + // modified afterward. Publication relies on the event: readers (GetTerminationReason) only access + // these after observing m_vmExitEvent signaled, so no lock is needed. Keeping them lock-free also + // avoids contending for m_lock from the HCS exit callback, which the destructor holds while the + // callback is drained. + WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown}; + std::wstring m_terminationDetails; }; // @@ -135,8 +142,6 @@ class WSLCVirtualMachineFactory // subsequent VMs reuse this duplicate, whose writes simply fail if the sink is gone. wil::unique_handle m_dmesgOutput; - wil::com_ptr m_terminationCallback; - ULONGLONG m_maximumStorageSizeMb{}; ULONG m_cpuCount{}; ULONG m_memoryMb{}; diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 7278e40385..2cd01f7d85 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -99,16 +99,6 @@ typedef enum _WSLCSignal WSLCSignalSIGSYS = 31 } WSLCSignal; -[ - uuid(7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFE), - pointer_default(unique), - object -] -interface ITerminationCallback : IUnknown -{ - HRESULT OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details); -}; - [ uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801), pointer_default(unique), @@ -536,6 +526,10 @@ interface IWSLCVirtualMachine : IUnknown // Returns an event that is signaled when the VM exits (graceful or forced). HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); } // @@ -576,7 +570,6 @@ typedef struct _WSLCSessionSettings { ULONG MemoryMb; ULONG BootTimeoutMs; WSLCNetworkingMode NetworkingMode; - [unique] ITerminationCallback* TerminationCallback; WSLCFeatureFlags FeatureFlags; WSLCHandle DmesgOutput; WSLCSessionStorageFlags StorageFlags; @@ -788,6 +781,15 @@ interface IWSLCSession : IUnknown HRESULT GetId([out] ULONG* Id); HRESULT GetState([out] WSLCSessionState* State); + // Returns a one-off event that is signaled when the session terminates, whether due to an + // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the + // caller and remains valid (and observes the signaled state) even after the session is released. + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); + // Image management. HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index edb9bbce12..99588eda95 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2621,9 +2621,20 @@ try if (m_vmExitedEvent && m_vmExitedEvent.is_signaled()) { WSL_LOG("SkippingGracefulShutdown_VmDead", TraceLoggingValue(m_id, "SessionId")); + + // The VM exited on its own, so it recorded the cause. + if (m_virtualMachine) + { + wil::unique_cotaskmem_string details; + LOG_IF_FAILED(m_virtualMachine->GetTerminationReason(&m_terminationReason, &details)); + m_terminationDetails = details ? details.get() : L""; + } } else { + // The VM is still alive, so this is a graceful shutdown initiated by us. + m_terminationReason = WSLCVirtualMachineTerminationReasonShutdown; + if (m_virtualMachine) { m_virtualMachine->OnSessionTerminated(); @@ -2662,7 +2673,8 @@ try m_swapVhdPath.clear(); } - m_terminated = true; + m_sessionTerminatedEvent.SetEvent(); + return S_OK; } CATCH_RETURN(); @@ -2960,10 +2972,43 @@ HRESULT WSLCSession::GetState(_Out_ WSLCSessionState* State) { RETURN_HR_IF_NULL(E_POINTER, State); - *State = m_terminated ? WSLCSessionStateTerminated : WSLCSessionStateRunning; + *State = m_sessionTerminatedEvent.is_signaled() ? WSLCSessionStateTerminated : WSLCSessionStateRunning; return S_OK; } +HRESULT WSLCSession::GetTerminationEvent(_Out_ HANDLE* Event) +try +{ + RETURN_HR_IF(E_POINTER, Event == nullptr); + + *Event = nullptr; + + // Duplicate the "terminated" event. The caller owns the returned handle, which stays valid even after the session is released. + *Event = wsl::windows::common::wslutil::DuplicateHandle(m_sessionTerminatedEvent.get(), SYNCHRONIZE); + + return S_OK; +} +CATCH_RETURN(); + +HRESULT WSLCSession::GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) +try +{ + RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr); + + *Reason = WSLCVirtualMachineTerminationReasonUnknown; + *Details = nullptr; + + // m_terminationReason/m_terminationDetails are written once before m_sessionTerminatedEvent is + // signaled and never modified afterward, so observing the signaled event safely publishes them. + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_sessionTerminatedEvent.is_signaled()); + + *Reason = m_terminationReason; + *Details = wil::make_cotaskmem_string(m_terminationDetails.c_str()).release(); + + return S_OK; +} +CATCH_RETURN(); + void WSLCSession::RecoverExistingContainers() { WI_ASSERT(m_dockerClient.has_value()); diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 5c7bee2227..d7dd3db53f 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -98,6 +98,8 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession IFACEMETHOD(GetId)(_Out_ ULONG* Id) override; IFACEMETHOD(GetState)(_Out_ WSLCSessionState* State) override; + IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override; + IFACEMETHOD(GetTerminationReason)(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) override; // Image management. IFACEMETHOD(PullImage)( @@ -271,7 +273,11 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession std::mutex m_networksLock; std::unordered_map m_networks; wil::unique_event m_sessionTerminatingEvent{wil::EventOptions::ManualReset}; + wil::unique_event m_sessionTerminatedEvent{wil::EventOptions::ManualReset}; wil::unique_event m_vmExitedEvent; + + WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown}; + std::wstring m_terminationDetails; wil::srwlock m_lock; IORelay m_ioRelay; std::optional m_containerdProcess; @@ -279,7 +285,6 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession WSLCFeatureFlags m_featureFlags{}; std::function m_destructionCallback; std::atomic m_terminating{false}; - std::atomic m_terminated{false}; wil::com_ptr m_pluginNotifier; diff --git a/src/windows/wslcsession/WSLCVirtualMachine.h b/src/windows/wslcsession/WSLCVirtualMachine.h index dbdf1e2483..f6e438c5c0 100644 --- a/src/windows/wslcsession/WSLCVirtualMachine.h +++ b/src/windows/wslcsession/WSLCVirtualMachine.h @@ -170,6 +170,12 @@ class WSLCVirtualMachine return m_vmTerminatingEvent.get(); } + // Retrieves the cached termination reason and details from the underlying VM. + HRESULT GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) const + { + return m_vm->GetTerminationReason(Reason, Details); + } + GUID VmId() const { return m_vmId; diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 50752292a6..0b86d2f5d7 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -2876,46 +2876,26 @@ class WSLCTests } } - WSLC_TEST_METHOD(TerminationCallback) + WSLC_TEST_METHOD(TerminationEvent) { - class DECLSPEC_UUID("7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFF") CallbackInstance - : public Microsoft::WRL::RuntimeClass, ITerminationCallback, IFastRundown> - { - - public: - CallbackInstance(std::function&& callback) : - m_callback(std::move(callback)) - { - } - - HRESULT OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details) override - { - m_callback(Reason, Details); - return S_OK; - } + auto session = CreateSession(GetDefaultSessionSettings(L"termination-event-test")); - private: - std::function m_callback; - }; + wil::unique_handle terminationEvent; + VERIFY_SUCCEEDED(session->GetTerminationEvent(&terminationEvent)); + VERIFY_IS_NOT_NULL(terminationEvent.get()); - std::promise> promise; + // The reason is unavailable until the session has terminated. + WSLCVirtualMachineTerminationReason reason{}; + wil::unique_cotaskmem_string details; + VERIFY_ARE_EQUAL(session->GetTerminationReason(&reason, &details), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - CallbackInstance callback{[&](WSLCVirtualMachineTerminationReason reason, LPCWSTR details) { - promise.set_value(std::make_pair(reason, details)); - }}; + // Terminating the session should signal the event and record a graceful shutdown reason. + VERIFY_SUCCEEDED(session->Terminate()); - WSLCSessionSettings sessionSettings = GetDefaultSessionSettings(L"termination-callback-test"); - sessionSettings.TerminationCallback = &callback; + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast(WAIT_OBJECT_0)); - auto session = CreateSession(sessionSettings); - - session.reset(); - auto future = promise.get_future(); - auto result = future.wait_for(std::chrono::seconds(30)); - VERIFY_ARE_EQUAL(result, std::future_status::ready); - auto [reason, details] = future.get(); + VERIFY_SUCCEEDED(session->GetTerminationReason(&reason, &details)); VERIFY_ARE_EQUAL(reason, WSLCVirtualMachineTerminationReasonShutdown); - VERIFY_ARE_NOT_EQUAL(details, L""); } WSLC_TEST_METHOD(CrashDumpCallback) diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index 442ffe8205..c635e9a3f6 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -267,60 +267,53 @@ class WslcSdkTests VERIFY_ARE_EQUAL(WslcCreateSession(nullptr, &session2, nullptr), E_POINTER); } - WSLC_TEST_METHOD(TerminationCallbackViaTerminate) + WSLC_TEST_METHOD(TerminationEventViaTerminate) { - std::promise promise; - - auto callback = [](WslcSessionTerminationReason reason, PVOID context) { - auto* p = static_cast*>(context); - p->set_value(reason); - }; - - std::filesystem::path extraStorage = m_storagePath / "wslc-termcb-term-storage"; + std::filesystem::path extraStorage = m_storagePath / "wslc-termevt-term-storage"; WslcSessionSettings sessionSettings; - VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termcb-term-test", extraStorage.c_str(), &sessionSettings)); + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termevt-term-test", extraStorage.c_str(), &sessionSettings)); VERIFY_SUCCEEDED(WslcSetSessionSettingsTimeout(&sessionSettings, 30 * 1000)); - VERIFY_SUCCEEDED(WslcSetSessionSettingsTerminationCallback(&sessionSettings, callback, &promise)); UniqueSession session; VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr)); - // Terminating the session should trigger a graceful shutdown and fire the callback. + wil::unique_handle terminationEvent; + VERIFY_SUCCEEDED(WslcGetSessionTerminationEvent(session.get(), &terminationEvent)); + VERIFY_IS_NOT_NULL(terminationEvent.get()); + + // Terminating the session should trigger a graceful shutdown and signal the event. VERIFY_SUCCEEDED(WslcTerminateSession(session.get())); - auto future = promise.get_future(); - VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(30)), std::future_status::ready); - VERIFY_ARE_EQUAL(future.get(), WSLC_SESSION_TERMINATION_REASON_SHUTDOWN); + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast(WAIT_OBJECT_0)); + + WslcSessionTerminationReason reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN; + VERIFY_SUCCEEDED(WslcGetSessionTerminationReason(session.get(), &reason)); + VERIFY_ARE_EQUAL(reason, WSLC_SESSION_TERMINATION_REASON_SHUTDOWN); } - WSLC_TEST_METHOD(TerminationCallbackViaRelease) + WSLC_TEST_METHOD(TerminationEventViaRelease) { - std::promise promise; - - auto callback = [](WslcSessionTerminationReason reason, PVOID context) { - auto* p = static_cast*>(context); - p->set_value(reason); - }; - - std::filesystem::path extraStorage = m_storagePath / "wslc-termcb-release-storage"; + std::filesystem::path extraStorage = m_storagePath / "wslc-termevt-release-storage"; WslcSessionSettings sessionSettings; - VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termcb-release-test", extraStorage.c_str(), &sessionSettings)); + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termevt-release-test", extraStorage.c_str(), &sessionSettings)); VERIFY_SUCCEEDED(WslcSetSessionSettingsTimeout(&sessionSettings, 30 * 1000)); - VERIFY_SUCCEEDED(WslcSetSessionSettingsTerminationCallback(&sessionSettings, callback, &promise)); UniqueSession session; VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr)); - // Releasing the session should trigger a graceful shutdown and fire the callback. + // The termination event is owned by the caller and stays valid even after the session is released. + wil::unique_handle terminationEvent; + VERIFY_SUCCEEDED(WslcGetSessionTerminationEvent(session.get(), &terminationEvent)); + VERIFY_IS_NOT_NULL(terminationEvent.get()); + + // Releasing the session should trigger a graceful shutdown and signal the event. VERIFY_SUCCEEDED(WslcReleaseSession(session.get())); - // Calling WslcSessionRelease will destroy the session + // Calling WslcReleaseSession will destroy the session. session.release(); - auto future = promise.get_future(); - VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(30)), std::future_status::ready); - VERIFY_ARE_EQUAL(future.get(), WSLC_SESSION_TERMINATION_REASON_SHUTDOWN); + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast(WAIT_OBJECT_0)); } WSLC_TEST_METHOD(CrashDumpCallback)