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