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)