From 1beaebcff88ac72d3fc46f4f9ac6b3fa8617dd5e Mon Sep 17 00:00:00 2001 From: kvega005 Date: Wed, 10 Jun 2026 15:44:51 -0700 Subject: [PATCH 1/5] Use event instead of termination callback --- msipackage/package.wix.in | 8 --- src/windows/WslcSDK/CMakeLists.txt | 2 - src/windows/WslcSDK/TerminationCallback.cpp | 58 ------------------- src/windows/WslcSDK/TerminationCallback.h | 32 ---------- src/windows/WslcSDK/WslcsdkPrivate.h | 3 - src/windows/WslcSDK/wslcsdk.cpp | 43 +++++++++----- src/windows/WslcSDK/wslcsdk.def | 3 +- src/windows/WslcSDK/wslcsdk.h | 9 +-- src/windows/service/exe/HcsVirtualMachine.cpp | 29 ++++++---- src/windows/service/exe/HcsVirtualMachine.h | 5 +- src/windows/service/inc/wslc.idl | 26 +++++---- src/windows/wslcsession/WSLCSession.cpp | 49 +++++++++++++++- src/windows/wslcsession/WSLCSession.h | 7 ++- src/windows/wslcsession/WSLCVirtualMachine.h | 6 ++ test/windows/WSLCTests.cpp | 46 +++++---------- test/windows/WslcSdkTests.cpp | 55 ++++++++---------- 16 files changed, 169 insertions(+), 212 deletions(-) delete mode 100644 src/windows/WslcSDK/TerminationCallback.cpp delete mode 100644 src/windows/WslcSDK/TerminationCallback.h diff --git a/msipackage/package.wix.in b/msipackage/package.wix.in index 2584248b68..f37c0d0dcb 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 d9f0b7e778..f1224d9891 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 wslcsdk.cpp WslcsdkPrivate.cpp ) @@ -9,7 +8,6 @@ set(HEADERS Defaults.h IOCallback.h ProgressCallback.h - TerminationCallback.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 36a4568931..3ff96a528a 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/wslcsdk.cpp b/src/windows/WslcSDK/wslcsdk.cpp index 69366079aa..581f091db8 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 "Localization.h" #include "WslInstall.h" #include "wslutil.h" @@ -434,12 +433,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); @@ -585,15 +578,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; } diff --git a/src/windows/WslcSDK/wslcsdk.def b/src/windows/WslcSDK/wslcsdk.def index 23a3a1c7fa..db42259c5f 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 2abbbc8b48..77b0e23783 100644 --- a/src/windows/WslcSDK/wslcsdk.h +++ b/src/windows/WslcSDK/wslcsdk.h @@ -42,7 +42,7 @@ EXTERN_C_START #define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */ // Session values -#define WSLC_SESSION_OPTIONS_SIZE 88 +#define WSLC_SESSION_OPTIONS_SIZE 72 #define WSLC_SESSION_OPTIONS_ALIGNMENT 8 typedef struct WslcSessionSettings @@ -123,8 +123,6 @@ typedef enum WslcSessionTerminationReason WSLC_SESSION_TERMINATION_REASON_CRASHED = 2, } WslcSessionTerminationReason; -typedef __callback void(CALLBACK* WslcSessionTerminationCallback)(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context); - STDAPI WslcInitSessionSettings(_In_ PCWSTR name, _In_ PCWSTR storagePath, _Out_ WslcSessionSettings* sessionSettings); STDAPI WslcCreateSession(_In_ WslcSessionSettings* sessionSettings, _Out_ WslcSession* session, _Outptr_opt_result_z_ PWSTR* errorMessage); @@ -138,9 +136,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 0bfb5062b0..424b9bd6f8 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. @@ -686,8 +680,6 @@ CATCH_LOG() void HcsVirtualMachine::OnExit(const HCS_EVENT* Event) { - m_vmExitEvent.SetEvent(); - const auto exitStatus = wsl::shared::FromJson(Event->EventData); auto reason = WSLCVirtualMachineTerminationReasonUnknown; @@ -709,11 +701,28 @@ void HcsVirtualMachine::OnExit(const HCS_EVENT* Event) } } - if (m_terminationCallback) + // Cache the termination reason and details before signaling the exit event. { - LOG_IF_FAILED(m_terminationCallback->OnTermination(reason, Event->EventData)); + std::lock_guard lock(m_lock); + 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); + + std::lock_guard lock(m_lock); + *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) { diff --git a/src/windows/service/exe/HcsVirtualMachine.h b/src/windows/service/exe/HcsVirtualMachine.h index b8eba94902..5589d90842 100644 --- a/src/windows/service/exe/HcsVirtualMachine.h +++ b/src/windows/service/exe/HcsVirtualMachine.h @@ -46,6 +46,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 @@ -102,7 +103,9 @@ class HcsVirtualMachine std::atomic m_vmSavedStateCaptured = false; std::atomic m_crashLogCaptured = false; - wil::com_ptr m_terminationCallback; + // Termination reason and details, cached when the VM exits (see OnExit). Guarded by m_lock. + WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown}; + std::wstring m_terminationDetails; }; } // namespace wsl::windows::service::wslc diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index a1a89dc9fd..1af8cf7d45 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -97,16 +97,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(5038842F-53DB-4F30-A6D0-A41B02C94AC1), pointer_default(unique), @@ -498,6 +488,11 @@ 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. The values are only meaningful + // after the termination event has been signaled; before that the reason is + // WSLCVirtualMachineTerminationReasonUnknown and Details is an empty string. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); } typedef enum _WSLCSessionStorageFlags @@ -518,7 +513,6 @@ typedef struct _WSLCSessionSettings { ULONG MemoryMb; ULONG BootTimeoutMs; WSLCNetworkingMode NetworkingMode; - [unique] ITerminationCallback* TerminationCallback; WSLCFeatureFlags FeatureFlags; WSLCHandle DmesgOutput; WSLCSessionStorageFlags StorageFlags; @@ -728,6 +722,16 @@ 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. The values are only meaningful after the + // termination event has been signaled; before that the reason is + // WSLCVirtualMachineTerminationReasonUnknown and Details is an empty string. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); + // Image management. HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback); 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 c08957ac0b..8726e73a82 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2479,9 +2479,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(); @@ -2520,7 +2531,8 @@ try m_swapVhdPath.clear(); } - m_terminated = true; + m_sessionTerminatedEvent.SetEvent(); + return S_OK; } CATCH_RETURN(); @@ -2739,9 +2751,42 @@ void WSLCSession::OnContainerDeleted(const WSLCContainerImpl* Container) HRESULT WSLCSession::GetState(_Out_ WSLCSessionState* 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); + + // Duplicate the "terminated" event (signaled once the session is fully done, via either an + // explicit Terminate() or an unexpected VM exit). The caller owns the returned handle, which + // stays valid (and observes the signaled state) even after the session is released. + *Event = wsl::windows::common::wslutil::DuplicateHandle(m_sessionTerminatedEvent.get()); + return S_OK; } +CATCH_RETURN(); + +HRESULT WSLCSession::GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) +try +{ + RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr); + + auto lock = m_lock.lock_shared(); + + // The termination reason is only populated once the session has finished terminating. Fail + // rather than returning a placeholder so callers can distinguish "still running" from + // "terminated for an unknown reason". + 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() { diff --git a/src/windows/wslcsession/WSLCSession.h b/src/windows/wslcsession/WSLCSession.h index 5a963140a8..dd924a7056 100644 --- a/src/windows/wslcsession/WSLCSession.h +++ b/src/windows/wslcsession/WSLCSession.h @@ -89,6 +89,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)(_In_ LPCSTR Image, _In_opt_ LPCSTR RegistryAuthenticationInformation, _In_opt_ IProgressCallback* ProgressCallback) override; @@ -229,7 +231,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; @@ -237,7 +243,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 a38588a74c..ad68629059 100644 --- a/src/windows/wslcsession/WSLCVirtualMachine.h +++ b/src/windows/wslcsession/WSLCVirtualMachine.h @@ -162,6 +162,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 0650cc26f6..f6aa743693 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -2711,46 +2711,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; - } - - private: - std::function m_callback; - }; + auto session = CreateSession(GetDefaultSessionSettings(L"termination-event-test")); - std::promise> promise; + wil::unique_handle terminationEvent; + VERIFY_SUCCEEDED(session->GetTerminationEvent(&terminationEvent)); + VERIFY_IS_NOT_NULL(terminationEvent.get()); - CallbackInstance callback{[&](WSLCVirtualMachineTerminationReason reason, LPCWSTR details) { - promise.set_value(std::make_pair(reason, details)); - }}; + // 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)); - WSLCSessionSettings sessionSettings = GetDefaultSessionSettings(L"termination-callback-test"); - sessionSettings.TerminationCallback = &callback; + // Terminating the session should signal the event and record a graceful shutdown reason. + VERIFY_SUCCEEDED(session->Terminate()); - auto session = CreateSession(sessionSettings); + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast(WAIT_OBJECT_0)); - 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(BuildImageStuckCallbackCancellation) diff --git a/test/windows/WslcSdkTests.cpp b/test/windows/WslcSdkTests.cpp index e676b4128e..8cce628dc7 100644 --- a/test/windows/WslcSdkTests.cpp +++ b/test/windows/WslcSdkTests.cpp @@ -263,60 +263,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)); } // ----------------------------------------------------------------------- From c9da34e51214f540e5d0ccc1ed3b78268dfdf3d0 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Wed, 10 Jun 2026 16:20:32 -0700 Subject: [PATCH 2/5] Fux build --- src/windows/WslcSDK/winrt/Session.cpp | 26 +++++++++++++++++++++++--- src/windows/WslcSDK/winrt/Session.h | 10 +++++++--- src/windows/WslcSDK/wslcsdk.cpp | 3 --- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/windows/WslcSDK/winrt/Session.cpp b/src/windows/WslcSDK/winrt/Session.cpp index 5a29437c99..1234b60094 100644 --- a/src/windows/WslcSDK/winrt/Session.cpp +++ b/src/windows/WslcSDK/winrt/Session.cpp @@ -45,6 +45,18 @@ Session::Session(winrt::Microsoft::WSL::Containers::SessionSettings const& setti } } +Session::~Session() +{ + // Stop watching the termination handle and drain any in-flight callback before the rest of the + // session is torn down. Releasing the session handle below can signal the termination event, so the + // wait must be cancelled first to avoid the callback running against a half-destructed object. + if (m_terminationWait) + { + SetThreadpoolWait(m_terminationWait.get(), nullptr, nullptr); + WaitForThreadpoolWaitCallbacks(m_terminationWait.get(), TRUE); + } +} + void Session::Start() { if (m_session) @@ -52,12 +64,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 +316,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..cac2d6488e 100644 --- a/src/windows/WslcSDK/winrt/Session.h +++ b/src/windows/WslcSDK/winrt/Session.h @@ -20,6 +20,7 @@ struct Session : SessionT { Session() = default; Session(winrt::Microsoft::WSL::Containers::SessionSettings const& settings); + ~Session(); void Start(); void Terminate(); @@ -45,12 +46,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 581f091db8..9d39744aaa 100644 --- a/src/windows/WslcSDK/wslcsdk.cpp +++ b/src/windows/WslcSDK/wslcsdk.cpp @@ -621,10 +621,7 @@ try { auto internalType = CheckAndGetInternalTypeUniquePointer(session); - // Intentionally destroy session before termination callback in the event that - // the termination callback ends up being invoked by session destruction. internalType->session.reset(); - internalType->terminationCallback.reset(); return S_OK; } From 2fb90c0268f5954c9da93e55f6215c984b178e2e Mon Sep 17 00:00:00 2001 From: kvega005 Date: Wed, 10 Jun 2026 16:37:45 -0700 Subject: [PATCH 3/5] Fix --- src/windows/wslcsession/WSLCSession.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 8726e73a82..0568347911 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2760,9 +2760,9 @@ try { RETURN_HR_IF(E_POINTER, Event == nullptr); - // Duplicate the "terminated" event (signaled once the session is fully done, via either an - // explicit Terminate() or an unexpected VM exit). The caller owns the returned handle, which - // stays valid (and observes the signaled state) even after the session is released. + *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()); return S_OK; @@ -2774,11 +2774,11 @@ try { RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr); + *Reason = WSLCVirtualMachineTerminationReasonUnknown; + *Details = nullptr; + auto lock = m_lock.lock_shared(); - // The termination reason is only populated once the session has finished terminating. Fail - // rather than returning a placeholder so callers can distinguish "still running" from - // "terminated for an unknown reason". RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_sessionTerminatedEvent.is_signaled()); *Reason = m_terminationReason; From be6b59cbda9a7e23485eb3586604874f6df11a7c Mon Sep 17 00:00:00 2001 From: kvega005 Date: Wed, 10 Jun 2026 16:43:16 -0700 Subject: [PATCH 4/5] Address feedback --- src/windows/wslcsession/WSLCSession.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 0568347911..0ac3c3a797 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2777,8 +2777,8 @@ try *Reason = WSLCVirtualMachineTerminationReasonUnknown; *Details = nullptr; - auto lock = m_lock.lock_shared(); - + // 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; From aa4bd4bc938b5d1f506e51ac8d85fa3bea493a15 Mon Sep 17 00:00:00 2001 From: kvega005 Date: Thu, 11 Jun 2026 15:10:58 -0700 Subject: [PATCH 5/5] Address feedback. --- src/windows/WslcSDK/winrt/Session.cpp | 12 ------------ src/windows/WslcSDK/winrt/Session.h | 1 - src/windows/wslcsession/WSLCSession.cpp | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/windows/WslcSDK/winrt/Session.cpp b/src/windows/WslcSDK/winrt/Session.cpp index 1234b60094..46bb7afdcc 100644 --- a/src/windows/WslcSDK/winrt/Session.cpp +++ b/src/windows/WslcSDK/winrt/Session.cpp @@ -45,18 +45,6 @@ Session::Session(winrt::Microsoft::WSL::Containers::SessionSettings const& setti } } -Session::~Session() -{ - // Stop watching the termination handle and drain any in-flight callback before the rest of the - // session is torn down. Releasing the session handle below can signal the termination event, so the - // wait must be cancelled first to avoid the callback running against a half-destructed object. - if (m_terminationWait) - { - SetThreadpoolWait(m_terminationWait.get(), nullptr, nullptr); - WaitForThreadpoolWaitCallbacks(m_terminationWait.get(), TRUE); - } -} - void Session::Start() { if (m_session) diff --git a/src/windows/WslcSDK/winrt/Session.h b/src/windows/WslcSDK/winrt/Session.h index cac2d6488e..f6e8f7bd14 100644 --- a/src/windows/WslcSDK/winrt/Session.h +++ b/src/windows/WslcSDK/winrt/Session.h @@ -20,7 +20,6 @@ struct Session : SessionT { Session() = default; Session(winrt::Microsoft::WSL::Containers::SessionSettings const& settings); - ~Session(); void Start(); void Terminate(); diff --git a/src/windows/wslcsession/WSLCSession.cpp b/src/windows/wslcsession/WSLCSession.cpp index 81272d86d0..d2fcde2a47 100644 --- a/src/windows/wslcsession/WSLCSession.cpp +++ b/src/windows/wslcsession/WSLCSession.cpp @@ -2979,7 +2979,7 @@ try *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()); + *Event = wsl::windows::common::wslutil::DuplicateHandle(m_sessionTerminatedEvent.get(), SYNCHRONIZE); return S_OK; }