@samitouri / QOSAMI-WSL / commits / 41976c33

Use event instead of termination callback (#40767)

Kevin Vega committed Jun 15, 2026 at 11:17 UTC 41976c33235b7ecc608a3463321b0fdd3b7efbbd
18 files changed +195 -227
msipackage/package.wix.in
-8
@@ -306,14 +306,6 @@
306 </RegistryKey>
307 </RegistryKey>
308
309 - <!-- ITerminationCallback-->
310 - <RegistryKey Root="HKCR" Key="Interface\{7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFE}">
311 - <RegistryValue Value="ITerminationCallback" Type="string" />
312 - <RegistryKey Key="ProxyStubClsid32">
313 - <RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
314 - </RegistryKey>
315 - </RegistryKey>
316 -
309 <!-- ICrashDumpCallback-->
310 <RegistryKey Root="HKCR" Key="Interface\{8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801}">
311 <RegistryValue Value="ICrashDumpCallback" Type="string" />
src/windows/WslcSDK/CMakeLists.txt
-2
@@ -1,7 +1,6 @@
1 set(SOURCES
2 IOCallback.cpp
3 ProgressCallback.cpp
4 - TerminationCallback.cpp
4 CrashDumpCallback.cpp
5 wslcsdk.cpp
6 WslcsdkPrivate.cpp
@@ -10,7 +9,6 @@ set(HEADERS
9 Defaults.h
10 IOCallback.h
11 ProgressCallback.h
13 - TerminationCallback.h
12 CrashDumpCallback.h
13 wslcsdk.h
14 WslcsdkPrivate.h
src/windows/WslcSDK/TerminationCallback.cpp deleted
-58
@@ -1,58 +0,0 @@
1 -/*++
2 -
3 -Copyright (c) Microsoft. All rights reserved.
4 -
5 -Module Name:
6 -
7 - TerminationCallback.cpp
8 -
9 -Abstract:
10 -
11 - Implementation of a type that implements ITerminationCallback.
12 -
13 ---*/
14 -#include "precomp.h"
15 -#include "TerminationCallback.h"
16 -
17 -namespace {
18 -WslcSessionTerminationReason ConvertReason(WSLCVirtualMachineTerminationReason Reason)
19 -{
20 - switch (Reason)
21 - {
22 - case WSLCVirtualMachineTerminationReasonShutdown:
23 - return WSLC_SESSION_TERMINATION_REASON_SHUTDOWN;
24 - case WSLCVirtualMachineTerminationReasonCrashed:
25 - return WSLC_SESSION_TERMINATION_REASON_CRASHED;
26 - default:
27 - return WSLC_SESSION_TERMINATION_REASON_UNKNOWN;
28 - }
29 -}
30 -} // namespace
31 -
32 -TerminationCallback::TerminationCallback(WslcSessionTerminationCallback callback, PVOID context) :
33 - m_callback(callback), m_context(context)
34 -{
35 -}
36 -
37 -// TODO: Details from the runtime are dropped; should the SDK callback function be updated to include the reasons string?
38 -HRESULT STDMETHODCALLTYPE TerminationCallback::OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR)
39 -{
40 - if (m_callback)
41 - {
42 - m_callback(ConvertReason(Reason), m_context);
43 - }
44 -
45 - return S_OK;
46 -}
47 -
48 -winrt::com_ptr<TerminationCallback> TerminationCallback::CreateIf(const WslcSessionOptionsInternal* options)
49 -{
50 - if (options->terminationCallback)
51 - {
52 - return winrt::make_self<TerminationCallback>(options->terminationCallback, options->terminationCallbackContext);
53 - }
54 - else
55 - {
56 - return nullptr;
57 - }
58 -}
src/windows/WslcSDK/TerminationCallback.h deleted
-32
@@ -1,32 +0,0 @@
1 -/*++
2 -
3 -Copyright (c) Microsoft. All rights reserved.
4 -
5 -Module Name:
6 -
7 - TerminationCallback.h
8 -
9 -Abstract:
10 -
11 - Header for a type that implements ITerminationCallback.
12 -
13 ---*/
14 -#pragma once
15 -#include "wslc.h"
16 -#include "wslcsdkprivate.h"
17 -#include <winrt/base.h>
18 -
19 -struct TerminationCallback : public winrt::implements<TerminationCallback, ITerminationCallback>
20 -{
21 - TerminationCallback(WslcSessionTerminationCallback callback, PVOID context);
22 -
23 - // ITerminationCallback
24 - HRESULT STDMETHODCALLTYPE OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details) override;
25 -
26 - // Creates a TerminationCallback if the options provides a callback.
27 - static winrt::com_ptr<TerminationCallback> CreateIf(const WslcSessionOptionsInternal* options);
28 -
29 -private:
30 - WslcSessionTerminationCallback m_callback = nullptr;
31 - PVOID m_context = nullptr;
32 -};
src/windows/WslcSDK/WslcsdkPrivate.h
-3
@@ -33,8 +33,6 @@ typedef struct WslcSessionOptionsInternal
33
34 WslcVhdRequirements vhdRequirements;
35 WslcSessionFeatureFlags featureFlags;
36 - WslcSessionTerminationCallback terminationCallback;
37 - PVOID terminationCallbackContext;
36 } WslcSessionOptionsInternal;
37
38 static_assert(sizeof(WslcSessionOptionsInternal) == WSLC_SESSION_OPTIONS_SIZE, "WSLC_SESSION_OPTIONS_INTERNAL size mismatch");
@@ -107,7 +105,6 @@ const WslcContainerOptionsInternal* GetInternalType(const WslcContainerSettings*
105 struct WslcSessionImpl
106 {
107 wil::com_ptr<IWSLCSession> session;
110 - wil::com_ptr<ITerminationCallback> terminationCallback;
108 };
109
110 WslcSessionImpl* GetInternalType(WslcSession handle);
src/windows/WslcSDK/winrt/Session.cpp
+11 -3
@@ -52,12 +52,16 @@ void Session::Start()
52 throw winrt::hresult_illegal_method_call(L"Session has already been started");
53 }
54
55 - winrt::check_hresult(WslcSetSessionSettingsTerminationCallback(GetStructPointer(m_settings), TerminatedCallback, /* context */ this));
56 -
55 wil::unique_cotaskmem_string errorMessage;
56 auto hr = WslcCreateSession(GetStructPointer(m_settings), m_session.put(), errorMessage.put());
57 THROW_MSG_IF_FAILED(hr, errorMessage);
58 m_settings = nullptr;
59 +
60 + winrt::check_hresult(WslcGetSessionTerminationEvent(m_session.get(), m_terminationEvent.put()));
61 +
62 + m_terminationWait.reset(CreateThreadpoolWait(&Session::OnTerminated, this, nullptr));
63 + THROW_LAST_ERROR_IF_NULL(m_terminationWait);
64 + SetThreadpoolWait(m_terminationWait.get(), m_terminationEvent.get(), nullptr);
65 }
66
67 void Session::EnsureStarted() const
@@ -300,11 +304,15 @@ WslcSession Session::ToHandle()
304 return m_session.get();
305 }
306
303 -void CALLBACK Session::TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept
307 +void CALLBACK Session::OnTerminated(PTP_CALLBACK_INSTANCE /* instance */, PVOID context, PTP_WAIT /* wait */, TP_WAIT_RESULT /* waitResult */) noexcept
308 {
309 try
310 {
311 auto session = static_cast<Session*>(context);
312 +
313 + WslcSessionTerminationReason reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN;
314 + LOG_IF_FAILED(WslcGetSessionTerminationReason(session->m_session.get(), &reason));
315 +
316 session->m_terminatedEvent(static_cast<SessionTerminationReason>(reason));
317 }
318 CATCH_LOG();
src/windows/WslcSDK/winrt/Session.h
+6 -3
@@ -45,12 +45,15 @@ private:
45 void EnsureStarted() const;
46 winrt::Microsoft::WSL::Containers::SessionSettings m_settings; // Only kept until Start() is called
47
48 - static void CALLBACK TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept;
48 + // Threadpool callback that raises the Terminated event once the session's termination handle is signaled.
49 + static void CALLBACK OnTerminated(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_WAIT wait, TP_WAIT_RESULT waitResult) noexcept;
50
50 - // Releasing the session handle may trigger the termination callback.
51 - // Keep these two in this order so that the session handle is released before the termination event is destructed.
51 winrt::event<winrt::Microsoft::WSL::Containers::SessionTerminationHandler> m_terminatedEvent;
52 wil::unique_any<WslcSession, decltype(&WslcReleaseSession), &WslcReleaseSession> m_session{nullptr};
53 +
54 + // Bridges the one-off termination event surfaced by the SDK to the WinRT Terminated event.
55 + wil::unique_handle m_terminationEvent;
56 + wil::unique_threadpool_wait m_terminationWait;
57 };
58 } // namespace winrt::Microsoft::WSL::Containers::implementation
59 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
src/windows/WslcSDK/wslcsdk.cpp
+30 -16
@@ -17,7 +17,6 @@ Abstract:
17 #include "WslcsdkPrivate.h"
18 #include "Defaults.h"
19 #include "ProgressCallback.h"
20 -#include "TerminationCallback.h"
20 #include "CrashDumpCallback.h"
21 #include "Localization.h"
22 #include "WslInstall.h"
@@ -435,12 +434,6 @@ try
434 runtimeSettings.MemoryMb = internalType->memoryMb;
435 runtimeSettings.BootTimeoutMs = internalType->timeoutMS;
436 runtimeSettings.NetworkingMode = WSLCNetworkingModeVirtioProxy;
438 - auto terminationCallback = TerminationCallback::CreateIf(internalType);
439 - if (terminationCallback)
440 - {
441 - result->terminationCallback.attach(terminationCallback.as<ITerminationCallback>().detach());
442 - runtimeSettings.TerminationCallback = terminationCallback.get();
443 - }
437 runtimeSettings.FeatureFlags = ConvertFlags(internalType->featureFlags);
438 WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsVirtioFs);
439 WI_SetFlag(runtimeSettings.FeatureFlags, WslcFeatureFlagsDnsTunneling);
@@ -587,15 +580,39 @@ try
580 }
581 CATCH_RETURN();
582
590 -STDAPI WslcSetSessionSettingsTerminationCallback(
591 - _In_ WslcSessionSettings* sessionSettings, _In_opt_ WslcSessionTerminationCallback terminationCallback, _In_opt_ PVOID terminationContext)
583 +STDAPI WslcGetSessionTerminationEvent(_In_ WslcSession session, _Out_ HANDLE* terminationEvent)
584 try
585 {
594 - auto internalType = CheckAndGetInternalType(sessionSettings);
595 - RETURN_HR_IF(E_INVALIDARG, terminationCallback == nullptr && terminationContext != nullptr);
586 + RETURN_HR_IF_NULL(E_POINTER, terminationEvent);
587 + *terminationEvent = nullptr;
588 +
589 + auto internalType = CheckAndGetInternalType(session);
590 + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session);
591 +
592 + RETURN_HR(internalType->session->GetTerminationEvent(terminationEvent));
593 +}
594 +CATCH_RETURN();
595 +
596 +STDAPI WslcGetSessionTerminationReason(_In_ WslcSession session, _Out_ WslcSessionTerminationReason* reason)
597 +try
598 +{
599 + static_assert(
600 + WSLC_SESSION_TERMINATION_REASON_UNKNOWN == WSLCVirtualMachineTerminationReasonUnknown &&
601 + WSLC_SESSION_TERMINATION_REASON_SHUTDOWN == WSLCVirtualMachineTerminationReasonShutdown &&
602 + WSLC_SESSION_TERMINATION_REASON_CRASHED == WSLCVirtualMachineTerminationReasonCrashed,
603 + "Termination reason enum values mismatch.");
604 +
605 + RETURN_HR_IF_NULL(E_POINTER, reason);
606 + *reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN;
607 +
608 + auto internalType = CheckAndGetInternalType(session);
609 + RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalType->session);
610 +
611 + WSLCVirtualMachineTerminationReason runtimeReason = WSLCVirtualMachineTerminationReasonUnknown;
612 + wil::unique_cotaskmem_string details;
613 + RETURN_IF_FAILED(internalType->session->GetTerminationReason(&runtimeReason, &details));
614
597 - internalType->terminationCallback = terminationCallback;
598 - internalType->terminationCallbackContext = terminationContext;
615 + *reason = static_cast<WslcSessionTerminationReason>(runtimeReason);
616
617 return S_OK;
618 }
@@ -649,10 +666,7 @@ try
666 {
667 auto internalType = CheckAndGetInternalTypeUniquePointer(session);
668
652 - // Drop the session before the termination callback, in case session destruction triggers
653 - // the termination callback.
669 internalType->session.reset();
655 - internalType->terminationCallback.reset();
670
671 return S_OK;
672 }
src/windows/WslcSDK/wslcsdk.def
+2 -1
@@ -16,7 +16,8 @@ WslcReleaseContainer
16 WslcReleaseProcess
17
18 WslcSetSessionSettingsFeatureFlags
19 -WslcSetSessionSettingsTerminationCallback
19 +WslcGetSessionTerminationEvent
20 +WslcGetSessionTerminationReason
21 WslcSetSessionSettingsCpuCount
22 WslcSetSessionSettingsMemory
23 WslcSetSessionSettingsTimeout
src/windows/WslcSDK/wslcsdk.h
+3 -6
@@ -43,7 +43,7 @@ EXTERN_C_START
43 #define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */
44
45 // Session values
46 -#define WSLC_SESSION_OPTIONS_SIZE 88
46 +#define WSLC_SESSION_OPTIONS_SIZE 72
47 #define WSLC_SESSION_OPTIONS_ALIGNMENT 8
48
49 typedef struct WslcSessionSettings
@@ -124,8 +124,6 @@ typedef enum WslcSessionTerminationReason
124 WSLC_SESSION_TERMINATION_REASON_CRASHED = 2,
125 } WslcSessionTerminationReason;
126
127 -typedef __callback void(CALLBACK* WslcSessionTerminationCallback)(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context);
128 -
127 typedef struct WslcSessionCrashDumpInfo
128 {
129 _Field_z_ PCWSTR dumpPath;
@@ -154,9 +152,8 @@ STDAPI WslcSetSessionSettingsVhd(_In_ WslcSessionSettings* sessionSettings, _In_
152
153 STDAPI WslcSetSessionSettingsFeatureFlags(_In_ WslcSessionSettings* sessionSettings, _In_ WslcSessionFeatureFlags flags);
154
157 -// Pass in Null for callback to clear the termination callback
158 -STDAPI WslcSetSessionSettingsTerminationCallback(
159 - _In_ WslcSessionSettings* sessionSettings, _In_opt_ WslcSessionTerminationCallback terminationCallback, _In_opt_ PVOID terminationContext);
155 +STDAPI WslcGetSessionTerminationEvent(_In_ WslcSession session, _Out_ HANDLE* terminationEvent);
156 +STDAPI WslcGetSessionTerminationReason(_In_ WslcSession session, _Out_ WslcSessionTerminationReason* reason);
157
158 STDAPI WslcTerminateSession(_In_ WslcSession session);
159 STDAPI WslcReleaseSession(_In_ WslcSession session);
src/windows/service/exe/HcsVirtualMachine.cpp
+26 -14
@@ -285,12 +285,6 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings)
285 m_guestDeviceManager = std::make_shared<::GuestDeviceManager>(m_vmIdString, m_vmId);
286 }
287
288 - // Configure termination callback
289 - if (Settings->TerminationCallback)
290 - {
291 - m_terminationCallback = Settings->TerminationCallback;
292 - }
293 -
288 hcs::RegisterCallback(m_computeSystem.get(), &HcsVirtualMachine::OnVmExitCallback, this);
289
290 // Create a listening socket for mini_init to connect to once the VM is running.
@@ -692,8 +686,6 @@ CATCH_LOG()
686
687 void HcsVirtualMachine::OnExit(const HCS_EVENT* Event)
688 {
695 - m_vmExitEvent.SetEvent();
696 -
689 const auto exitStatus = wsl::shared::FromJson<wsl::windows::common::hcs::SystemExitStatus>(Event->EventData);
690
691 auto reason = WSLCVirtualMachineTerminationReasonUnknown;
@@ -715,12 +707,34 @@ void HcsVirtualMachine::OnExit(const HCS_EVENT* Event)
707 }
708 }
709
718 - if (m_terminationCallback)
719 - {
720 - LOG_IF_FAILED(m_terminationCallback->OnTermination(reason, Event->EventData));
721 - }
710 + // Cache the termination reason and details before signaling the exit event. These fields are
711 + // written once here (OnExit fires once and m_vmExitEvent is never reset) and published to readers
712 + // by the SetEvent below; GetTerminationReason only reads them after observing the signaled event.
713 + m_terminationReason = reason;
714 + m_terminationDetails = Event->EventData;
715 +
716 + m_vmExitEvent.SetEvent();
717 }
718
719 +HRESULT HcsVirtualMachine::GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details)
720 +try
721 +{
722 + RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr);
723 +
724 + *Reason = WSLCVirtualMachineTerminationReasonUnknown;
725 + *Details = nullptr;
726 +
727 + // m_terminationReason/m_terminationDetails are written once in OnExit before m_vmExitEvent is
728 + // signaled and never modified afterward, so observing the signaled event safely publishes them.
729 + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_vmExitEvent.is_signaled());
730 +
731 + *Reason = m_terminationReason;
732 + *Details = wil::make_cotaskmem_string(m_terminationDetails.c_str()).release();
733 +
734 + return S_OK;
735 +}
736 +CATCH_RETURN()
737 +
738 void HcsVirtualMachine::OnCrash(const HCS_EVENT* Event)
739 {
740 if (m_crashLogCaptured.load() && m_vmSavedStateCaptured.load())
@@ -863,7 +877,6 @@ WSLCVirtualMachineFactory::WSLCVirtualMachineFactory(_In_ const WSLCSessionSetti
877 m_dmesgOutput.reset(wslutil::DuplicateHandle(wslutil::FromCOMInputHandle(Settings->DmesgOutput), GENERIC_WRITE | SYNCHRONIZE));
878 }
879
866 - m_terminationCallback = Settings->TerminationCallback;
880 m_maximumStorageSizeMb = Settings->MaximumStorageSizeMb;
881 m_cpuCount = Settings->CpuCount;
882 m_memoryMb = Settings->MemoryMb;
@@ -883,7 +896,6 @@ WSLCSessionSettings WSLCVirtualMachineFactory::BuildSettings()
896 settings.MemoryMb = m_memoryMb;
897 settings.BootTimeoutMs = m_bootTimeoutMs;
898 settings.NetworkingMode = m_networkingMode;
886 - settings.TerminationCallback = m_terminationCallback.get();
899 settings.FeatureFlags = m_featureFlags;
900 settings.StorageFlags = m_storageFlags;
901 settings.RootVhdOverride = m_rootVhdOverride ? m_rootVhdOverride->c_str() : nullptr;
src/windows/service/exe/HcsVirtualMachine.h
+8 -3
@@ -48,6 +48,7 @@ public:
48 IFACEMETHOD(RemoveShare)(_In_ REFGUID ShareId) override;
49 IFACEMETHOD(ApplyGuestCapabilities)(_In_ const WSLCGuestCapabilities* Capabilities) override;
50 IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override;
51 + IFACEMETHOD(GetTerminationReason)(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) override;
52
53 private:
54 struct DiskInfo
@@ -103,7 +104,13 @@ private:
104 std::atomic<bool> m_vmSavedStateCaptured = false;
105 std::atomic<bool> m_crashLogCaptured = false;
106
106 - wil::com_ptr<ITerminationCallback> m_terminationCallback;
107 + // Termination reason and details, cached in OnExit before m_vmExitEvent is signaled and never
108 + // modified afterward. Publication relies on the event: readers (GetTerminationReason) only access
109 + // these after observing m_vmExitEvent signaled, so no lock is needed. Keeping them lock-free also
110 + // avoids contending for m_lock from the HCS exit callback, which the destructor holds while the
111 + // callback is drained.
112 + WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown};
113 + std::wstring m_terminationDetails;
114 };
115
116 //
@@ -135,8 +142,6 @@ private:
142 // subsequent VMs reuse this duplicate, whose writes simply fail if the sink is gone.
143 wil::unique_handle m_dmesgOutput;
144
138 - wil::com_ptr<ITerminationCallback> m_terminationCallback;
139 -
145 ULONGLONG m_maximumStorageSizeMb{};
146 ULONG m_cpuCount{};
147 ULONG m_memoryMb{};
src/windows/service/inc/wslc.idl
+13 -11
@@ -99,16 +99,6 @@ typedef enum _WSLCSignal
99 WSLCSignalSIGSYS = 31
100 } WSLCSignal;
101
102 -[
103 - uuid(7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFE),
104 - pointer_default(unique),
105 - object
106 -]
107 -interface ITerminationCallback : IUnknown
108 -{
109 - HRESULT OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details);
110 -};
111 -
102 [
103 uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801),
104 pointer_default(unique),
@@ -536,6 +526,10 @@ interface IWSLCVirtualMachine : IUnknown
526
527 // Returns an event that is signaled when the VM exits (graceful or forced).
528 HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
529 +
530 + // Returns the cached termination reason and details. These are only available after the
531 + // termination event has been signaled; before that the call fails.
532 + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
533 }
534
535 //
@@ -576,7 +570,6 @@ typedef struct _WSLCSessionSettings {
570 ULONG MemoryMb;
571 ULONG BootTimeoutMs;
572 WSLCNetworkingMode NetworkingMode;
579 - [unique] ITerminationCallback* TerminationCallback;
573 WSLCFeatureFlags FeatureFlags;
574 WSLCHandle DmesgOutput;
575 WSLCSessionStorageFlags StorageFlags;
@@ -788,6 +781,15 @@ interface IWSLCSession : IUnknown
781 HRESULT GetId([out] ULONG* Id);
782 HRESULT GetState([out] WSLCSessionState* State);
783
784 + // Returns a one-off event that is signaled when the session terminates, whether due to an
785 + // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the
786 + // caller and remains valid (and observes the signaled state) even after the session is released.
787 + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
788 +
789 + // Returns the cached termination reason and details. These are only available after the
790 + // termination event has been signaled; before that the call fails.
791 + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
792 +
793 // Image management.
794 HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
795 HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
src/windows/wslcsession/WSLCSession.cpp
+47 -2
@@ -2621,9 +2621,20 @@ try
2621 if (m_vmExitedEvent && m_vmExitedEvent.is_signaled())
2622 {
2623 WSL_LOG("SkippingGracefulShutdown_VmDead", TraceLoggingValue(m_id, "SessionId"));
2624 +
2625 + // The VM exited on its own, so it recorded the cause.
2626 + if (m_virtualMachine)
2627 + {
2628 + wil::unique_cotaskmem_string details;
2629 + LOG_IF_FAILED(m_virtualMachine->GetTerminationReason(&m_terminationReason, &details));
2630 + m_terminationDetails = details ? details.get() : L"";
2631 + }
2632 }
2633 else
2634 {
2635 + // The VM is still alive, so this is a graceful shutdown initiated by us.
2636 + m_terminationReason = WSLCVirtualMachineTerminationReasonShutdown;
2637 +
2638 if (m_virtualMachine)
2639 {
2640 m_virtualMachine->OnSessionTerminated();
@@ -2662,7 +2673,8 @@ try
2673 m_swapVhdPath.clear();
2674 }
2675
2665 - m_terminated = true;
2676 + m_sessionTerminatedEvent.SetEvent();
2677 +
2678 return S_OK;
2679 }
2680 CATCH_RETURN();
@@ -2960,10 +2972,43 @@ HRESULT WSLCSession::GetState(_Out_ WSLCSessionState* State)
2972 {
2973 RETURN_HR_IF_NULL(E_POINTER, State);
2974
2963 - *State = m_terminated ? WSLCSessionStateTerminated : WSLCSessionStateRunning;
2975 + *State = m_sessionTerminatedEvent.is_signaled() ? WSLCSessionStateTerminated : WSLCSessionStateRunning;
2976 return S_OK;
2977 }
2978
2979 +HRESULT WSLCSession::GetTerminationEvent(_Out_ HANDLE* Event)
2980 +try
2981 +{
2982 + RETURN_HR_IF(E_POINTER, Event == nullptr);
2983 +
2984 + *Event = nullptr;
2985 +
2986 + // Duplicate the "terminated" event. The caller owns the returned handle, which stays valid even after the session is released.
2987 + *Event = wsl::windows::common::wslutil::DuplicateHandle(m_sessionTerminatedEvent.get(), SYNCHRONIZE);
2988 +
2989 + return S_OK;
2990 +}
2991 +CATCH_RETURN();
2992 +
2993 +HRESULT WSLCSession::GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details)
2994 +try
2995 +{
2996 + RETURN_HR_IF(E_POINTER, Reason == nullptr || Details == nullptr);
2997 +
2998 + *Reason = WSLCVirtualMachineTerminationReasonUnknown;
2999 + *Details = nullptr;
3000 +
3001 + // m_terminationReason/m_terminationDetails are written once before m_sessionTerminatedEvent is
3002 + // signaled and never modified afterward, so observing the signaled event safely publishes them.
3003 + RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_sessionTerminatedEvent.is_signaled());
3004 +
3005 + *Reason = m_terminationReason;
3006 + *Details = wil::make_cotaskmem_string(m_terminationDetails.c_str()).release();
3007 +
3008 + return S_OK;
3009 +}
3010 +CATCH_RETURN();
3011 +
3012 void WSLCSession::RecoverExistingContainers()
3013 {
3014 WI_ASSERT(m_dockerClient.has_value());
src/windows/wslcsession/WSLCSession.h
+6 -1
@@ -98,6 +98,8 @@ public:
98
99 IFACEMETHOD(GetId)(_Out_ ULONG* Id) override;
100 IFACEMETHOD(GetState)(_Out_ WSLCSessionState* State) override;
101 + IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override;
102 + IFACEMETHOD(GetTerminationReason)(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) override;
103
104 // Image management.
105 IFACEMETHOD(PullImage)(
@@ -271,7 +273,11 @@ private:
273 std::mutex m_networksLock;
274 std::unordered_map<std::string, NetworkEntry> m_networks;
275 wil::unique_event m_sessionTerminatingEvent{wil::EventOptions::ManualReset};
276 + wil::unique_event m_sessionTerminatedEvent{wil::EventOptions::ManualReset};
277 wil::unique_event m_vmExitedEvent;
278 +
279 + WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown};
280 + std::wstring m_terminationDetails;
281 wil::srwlock m_lock;
282 IORelay m_ioRelay;
283 std::optional<ServiceRunningProcess> m_containerdProcess;
@@ -279,7 +285,6 @@ private:
285 WSLCFeatureFlags m_featureFlags{};
286 std::function<void()> m_destructionCallback;
287 std::atomic<bool> m_terminating{false};
282 - std::atomic<bool> m_terminated{false};
288
289 wil::com_ptr<IWSLCPluginNotifier> m_pluginNotifier;
290
src/windows/wslcsession/WSLCVirtualMachine.h
+6
@@ -170,6 +170,12 @@ public:
170 return m_vmTerminatingEvent.get();
171 }
172
173 + // Retrieves the cached termination reason and details from the underlying VM.
174 + HRESULT GetTerminationReason(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) const
175 + {
176 + return m_vm->GetTerminationReason(Reason, Details);
177 + }
178 +
179 GUID VmId() const
180 {
181 return m_vmId;
test/windows/WSLCTests.cpp
+13 -33
@@ -2876,46 +2876,26 @@ class WSLCTests
2876 }
2877 }
2878
2879 - WSLC_TEST_METHOD(TerminationCallback)
2879 + WSLC_TEST_METHOD(TerminationEvent)
2880 {
2881 - class DECLSPEC_UUID("7BC4E198-6531-4FA6-ADE2-5EF3D2A04DFF") CallbackInstance
2882 - : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, ITerminationCallback, IFastRundown>
2883 - {
2884 -
2885 - public:
2886 - CallbackInstance(std::function<void(WSLCVirtualMachineTerminationReason, LPCWSTR)>&& callback) :
2887 - m_callback(std::move(callback))
2888 - {
2889 - }
2890 -
2891 - HRESULT OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details) override
2892 - {
2893 - m_callback(Reason, Details);
2894 - return S_OK;
2895 - }
2881 + auto session = CreateSession(GetDefaultSessionSettings(L"termination-event-test"));
2882
2897 - private:
2898 - std::function<void(WSLCVirtualMachineTerminationReason, LPCWSTR)> m_callback;
2899 - };
2883 + wil::unique_handle terminationEvent;
2884 + VERIFY_SUCCEEDED(session->GetTerminationEvent(&terminationEvent));
2885 + VERIFY_IS_NOT_NULL(terminationEvent.get());
2886
2901 - std::promise<std::pair<WSLCVirtualMachineTerminationReason, std::wstring>> promise;
2887 + // The reason is unavailable until the session has terminated.
2888 + WSLCVirtualMachineTerminationReason reason{};
2889 + wil::unique_cotaskmem_string details;
2890 + VERIFY_ARE_EQUAL(session->GetTerminationReason(&reason, &details), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
2891
2903 - CallbackInstance callback{[&](WSLCVirtualMachineTerminationReason reason, LPCWSTR details) {
2904 - promise.set_value(std::make_pair(reason, details));
2905 - }};
2892 + // Terminating the session should signal the event and record a graceful shutdown reason.
2893 + VERIFY_SUCCEEDED(session->Terminate());
2894
2907 - WSLCSessionSettings sessionSettings = GetDefaultSessionSettings(L"termination-callback-test");
2908 - sessionSettings.TerminationCallback = &callback;
2895 + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast<DWORD>(WAIT_OBJECT_0));
2896
2910 - auto session = CreateSession(sessionSettings);
2911 -
2912 - session.reset();
2913 - auto future = promise.get_future();
2914 - auto result = future.wait_for(std::chrono::seconds(30));
2915 - VERIFY_ARE_EQUAL(result, std::future_status::ready);
2916 - auto [reason, details] = future.get();
2897 + VERIFY_SUCCEEDED(session->GetTerminationReason(&reason, &details));
2898 VERIFY_ARE_EQUAL(reason, WSLCVirtualMachineTerminationReasonShutdown);
2918 - VERIFY_ARE_NOT_EQUAL(details, L"");
2899 }
2900
2901 WSLC_TEST_METHOD(CrashDumpCallback)
test/windows/WslcSdkTests.cpp
+24 -31
@@ -268,60 +268,53 @@ class WslcSdkTests
268 VERIFY_ARE_EQUAL(WslcCreateSession(nullptr, &session2, nullptr), E_POINTER);
269 }
270
271 - WSLC_TEST_METHOD(TerminationCallbackViaTerminate)
271 + WSLC_TEST_METHOD(TerminationEventViaTerminate)
272 {
273 - std::promise<WslcSessionTerminationReason> promise;
274 -
275 - auto callback = [](WslcSessionTerminationReason reason, PVOID context) {
276 - auto* p = static_cast<std::promise<WslcSessionTerminationReason>*>(context);
277 - p->set_value(reason);
278 - };
279 -
280 - std::filesystem::path extraStorage = m_storagePath / "wslc-termcb-term-storage";
273 + std::filesystem::path extraStorage = m_storagePath / "wslc-termevt-term-storage";
274
275 WslcSessionSettings sessionSettings;
283 - VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termcb-term-test", extraStorage.c_str(), &sessionSettings));
276 + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termevt-term-test", extraStorage.c_str(), &sessionSettings));
277 VERIFY_SUCCEEDED(WslcSetSessionSettingsTimeout(&sessionSettings, 30 * 1000));
285 - VERIFY_SUCCEEDED(WslcSetSessionSettingsTerminationCallback(&sessionSettings, callback, &promise));
278
279 UniqueSession session;
280 VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr));
281
290 - // Terminating the session should trigger a graceful shutdown and fire the callback.
282 + wil::unique_handle terminationEvent;
283 + VERIFY_SUCCEEDED(WslcGetSessionTerminationEvent(session.get(), &terminationEvent));
284 + VERIFY_IS_NOT_NULL(terminationEvent.get());
285 +
286 + // Terminating the session should trigger a graceful shutdown and signal the event.
287 VERIFY_SUCCEEDED(WslcTerminateSession(session.get()));
288
293 - auto future = promise.get_future();
294 - VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(30)), std::future_status::ready);
295 - VERIFY_ARE_EQUAL(future.get(), WSLC_SESSION_TERMINATION_REASON_SHUTDOWN);
289 + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast<DWORD>(WAIT_OBJECT_0));
290 +
291 + WslcSessionTerminationReason reason = WSLC_SESSION_TERMINATION_REASON_UNKNOWN;
292 + VERIFY_SUCCEEDED(WslcGetSessionTerminationReason(session.get(), &reason));
293 + VERIFY_ARE_EQUAL(reason, WSLC_SESSION_TERMINATION_REASON_SHUTDOWN);
294 }
295
298 - WSLC_TEST_METHOD(TerminationCallbackViaRelease)
296 + WSLC_TEST_METHOD(TerminationEventViaRelease)
297 {
300 - std::promise<WslcSessionTerminationReason> promise;
301 -
302 - auto callback = [](WslcSessionTerminationReason reason, PVOID context) {
303 - auto* p = static_cast<std::promise<WslcSessionTerminationReason>*>(context);
304 - p->set_value(reason);
305 - };
306 -
307 - std::filesystem::path extraStorage = m_storagePath / "wslc-termcb-release-storage";
298 + std::filesystem::path extraStorage = m_storagePath / "wslc-termevt-release-storage";
299
300 WslcSessionSettings sessionSettings;
310 - VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termcb-release-test", extraStorage.c_str(), &sessionSettings));
301 + VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-termevt-release-test", extraStorage.c_str(), &sessionSettings));
302 VERIFY_SUCCEEDED(WslcSetSessionSettingsTimeout(&sessionSettings, 30 * 1000));
312 - VERIFY_SUCCEEDED(WslcSetSessionSettingsTerminationCallback(&sessionSettings, callback, &promise));
303
304 UniqueSession session;
305 VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr));
306
317 - // Releasing the session should trigger a graceful shutdown and fire the callback.
307 + // The termination event is owned by the caller and stays valid even after the session is released.
308 + wil::unique_handle terminationEvent;
309 + VERIFY_SUCCEEDED(WslcGetSessionTerminationEvent(session.get(), &terminationEvent));
310 + VERIFY_IS_NOT_NULL(terminationEvent.get());
311 +
312 + // Releasing the session should trigger a graceful shutdown and signal the event.
313 VERIFY_SUCCEEDED(WslcReleaseSession(session.get()));
319 - // Calling WslcSessionRelease will destroy the session
314 + // Calling WslcReleaseSession will destroy the session.
315 session.release();
316
322 - auto future = promise.get_future();
323 - VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(30)), std::future_status::ready);
324 - VERIFY_ARE_EQUAL(future.get(), WSLC_SESSION_TERMINATION_REASON_SHUTDOWN);
317 + VERIFY_ARE_EQUAL(WaitForSingleObject(terminationEvent.get(), 30 * 1000), static_cast<DWORD>(WAIT_OBJECT_0));
318 }
319
320 WSLC_TEST_METHOD(CrashDumpCallback)