wslc: introduce IWSLCVirtualMachineFactory for session VM creation (#40770)

The SYSTEM service previously created the VM up front and handed the pre-created IWSLCVirtualMachine to the per-user session process. This introduces IWSLCVirtualMachineFactory: the service now creates a factory (WSLCVirtualMachineFactory, which owns a deep copy of the VM settings) and passes it to the session, which creates the VM through the factory. This is behavior-preserving - the session still creates exactly one VM eagerly during Initialize - but funneling creation through the factory lets the session own when VMs are created. That is the foundation for letting a session recreate its VM later (e.g. after idle-termination). - wslc.idl: add IWSLCVirtualMachineFactory; change IWSLCSession::Initialize and IWSLCSessionFactory::CreateSession to take IWSLCVirtualMachineFactory*. - HcsVirtualMachine.{h,cpp}: implement WSLCVirtualMachineFactory. - WSLCSessionManager.cpp: create the factory instead of the VM. - WSLCSession/WSLCSessionFactory: create the VM via the factory. - package.wix.in: register the new interface IID for COM marshaling. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed Jun 11, 2026 at 12:19 UTC e76cb8b0bcb593e0858da561f18eddd87500e2f8
9 files changed +173 -18
msipackage/package.wix.in
+8
@@ -370,6 +370,14 @@
370 </RegistryKey>
371 </RegistryKey>
372
373 + <!-- IWSLCVirtualMachineFactory-->
374 + <RegistryKey Root="HKCR" Key="Interface\{2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B}">
375 + <RegistryValue Value="IWSLCVirtualMachineFactory" Type="string" />
376 + <RegistryKey Key="ProxyStubClsid32">
377 + <RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
378 + </RegistryKey>
379 + </RegistryKey>
380 +
381 <!-- IWSLCPluginNotifier-->
382 <RegistryKey Root="HKCR" Key="Interface\{F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2}">
383 <RegistryValue Value="IWSLCPluginNotifier" Type="string" />
src/windows/service/exe/HcsVirtualMachine.cpp
+77 -1
@@ -836,4 +836,80 @@ void HcsVirtualMachine::FreeLun(ULONG Lun)
836 THROW_HR_IF(E_INVALIDARG, !m_lunBitmap[Lun]);
837
838 m_lunBitmap[Lun] = false;
839 -}
\ No newline at end of file
839 +}
840 +
841 +namespace wsl::windows::service::wslc {
842 +
843 +WSLCVirtualMachineFactory::WSLCVirtualMachineFactory(_In_ const WSLCSessionSettings* Settings)
844 +{
845 + THROW_HR_IF(E_POINTER, Settings == nullptr);
846 +
847 + m_displayName = Settings->DisplayName ? Settings->DisplayName : L"";
848 + m_storagePath = Settings->StoragePath ? Settings->StoragePath : L"";
849 +
850 + if (Settings->RootVhdOverride != nullptr)
851 + {
852 + m_rootVhdOverride.emplace(Settings->RootVhdOverride);
853 + }
854 +
855 + if (Settings->RootVhdTypeOverride != nullptr)
856 + {
857 + m_rootVhdTypeOverride.emplace(Settings->RootVhdTypeOverride);
858 + }
859 +
860 + // Keep our own duplicate of the dmesg sink so recreated VMs can reuse it.
861 + if (Settings->DmesgOutput.Handle.File != nullptr && Settings->DmesgOutput.Handle.File != INVALID_HANDLE_VALUE)
862 + {
863 + m_dmesgOutput.reset(wslutil::DuplicateHandle(wslutil::FromCOMInputHandle(Settings->DmesgOutput), GENERIC_WRITE | SYNCHRONIZE));
864 + }
865 +
866 + m_terminationCallback = Settings->TerminationCallback;
867 + m_maximumStorageSizeMb = Settings->MaximumStorageSizeMb;
868 + m_cpuCount = Settings->CpuCount;
869 + m_memoryMb = Settings->MemoryMb;
870 + m_bootTimeoutMs = Settings->BootTimeoutMs;
871 + m_networkingMode = Settings->NetworkingMode;
872 + m_featureFlags = Settings->FeatureFlags;
873 + m_storageFlags = Settings->StorageFlags;
874 +}
875 +
876 +WSLCSessionSettings WSLCVirtualMachineFactory::BuildSettings()
877 +{
878 + WSLCSessionSettings settings{};
879 + settings.DisplayName = m_displayName.c_str();
880 + settings.StoragePath = m_storagePath.empty() ? nullptr : m_storagePath.c_str();
881 + settings.MaximumStorageSizeMb = m_maximumStorageSizeMb;
882 + settings.CpuCount = m_cpuCount;
883 + settings.MemoryMb = m_memoryMb;
884 + settings.BootTimeoutMs = m_bootTimeoutMs;
885 + settings.NetworkingMode = m_networkingMode;
886 + settings.TerminationCallback = m_terminationCallback.get();
887 + settings.FeatureFlags = m_featureFlags;
888 + settings.StorageFlags = m_storageFlags;
889 + settings.RootVhdOverride = m_rootVhdOverride ? m_rootVhdOverride->c_str() : nullptr;
890 + settings.RootVhdTypeOverride = m_rootVhdTypeOverride ? m_rootVhdTypeOverride->c_str() : nullptr;
891 +
892 + if (m_dmesgOutput)
893 + {
894 + settings.DmesgOutput = wslutil::ToCOMInputHandle(m_dmesgOutput.get());
895 + }
896 +
897 + return settings;
898 +}
899 +
900 +HRESULT WSLCVirtualMachineFactory::CreateVirtualMachine(_Out_ IWSLCVirtualMachine** Vm)
901 +try
902 +{
903 + RETURN_HR_IF(E_POINTER, Vm == nullptr);
904 + *Vm = nullptr;
905 +
906 + const auto settings = BuildSettings();
907 + auto vm = Microsoft::WRL::Make<HcsVirtualMachine>(&settings);
908 + THROW_IF_NULL_ALLOC(vm);
909 +
910 + *Vm = vm.Detach();
911 + return S_OK;
912 +}
913 +CATCH_RETURN()
914 +
915 +} // namespace wsl::windows::service::wslc
\ No newline at end of file
src/windows/service/exe/HcsVirtualMachine.h
+42
@@ -24,6 +24,8 @@ Abstract:
24 #include "WslCoreConfig.h"
25 #include <filesystem>
26 #include <map>
27 +#include <optional>
28 +#include <string>
29
30 #define MAX_VHD_COUNT 254
31
@@ -105,4 +107,44 @@ private:
107 wil::com_ptr<ITerminationCallback> m_terminationCallback;
108 };
109
110 +//
111 +// WSLCVirtualMachineFactory - Implements IWSLCVirtualMachineFactory.
112 +//
113 +// Owns a deep copy of the WSLCSessionSettings needed to construct a VM and creates a
114 +// fresh HcsVirtualMachine on demand. This lets the per-user session recreate a VM that
115 +// was idle-terminated, without the SYSTEM service holding a VM up front.
116 +//
117 +class WSLCVirtualMachineFactory
118 + : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLCVirtualMachineFactory, IFastRundown>
119 +{
120 +public:
121 + explicit WSLCVirtualMachineFactory(_In_ const WSLCSessionSettings* Settings);
122 +
123 + IFACEMETHOD(CreateVirtualMachine)(_Out_ IWSLCVirtualMachine** Vm) override;
124 +
125 +private:
126 + // Rebuilds a WSLCSessionSettings that points at this factory's owned storage.
127 + // The returned struct is only valid while this factory is alive.
128 + WSLCSessionSettings BuildSettings();
129 +
130 + std::wstring m_displayName;
131 + std::wstring m_storagePath;
132 + std::optional<std::wstring> m_rootVhdOverride;
133 + std::optional<std::string> m_rootVhdTypeOverride;
134 +
135 + // Duplicated dmesg sink (best-effort): only the first VM is guaranteed a live sink;
136 + // subsequent VMs reuse this duplicate, whose writes simply fail if the sink is gone.
137 + wil::unique_handle m_dmesgOutput;
138 +
139 + wil::com_ptr<ITerminationCallback> m_terminationCallback;
140 +
141 + ULONGLONG m_maximumStorageSizeMb{};
142 + ULONG m_cpuCount{};
143 + ULONG m_memoryMb{};
144 + ULONG m_bootTimeoutMs{};
145 + WSLCNetworkingMode m_networkingMode{};
146 + WSLCFeatureFlags m_featureFlags{};
147 + WSLCSessionStorageFlags m_storageFlags{};
148 +};
149 +
150 } // namespace wsl::windows::service::wslc
src/windows/service/exe/WSLCSessionManager.cpp
+6 -3
@@ -45,6 +45,7 @@ using wsl::windows::service::wslc::CallingProcessTokenInfo;
45 using wsl::windows::service::wslc::HcsVirtualMachine;
46 using wsl::windows::service::wslc::WSLCPluginNotifier;
47 using wsl::windows::service::wslc::WSLCSessionManagerImpl;
48 +using wsl::windows::service::wslc::WSLCVirtualMachineFactory;
49 namespace wslutil = wsl::windows::common::wslutil;
50 namespace settings = wsl::windows::wslc::settings;
51
@@ -268,8 +269,10 @@ void WSLCSessionManagerImpl::CreateSession(
269 notifier = wil::MakeOrThrow<WSLCPluginNotifier>(
270 g_pluginManager, sessionId, creatorPid, std::wstring(resolvedDisplayName), wil::shared_handle(sharedToken), std::vector<BYTE>(storedSid));
271
271 - // Create the VM in the SYSTEM service (privileged).
272 - auto vm = Microsoft::WRL::Make<HcsVirtualMachine>(Settings);
272 + // Create the VM factory in the SYSTEM service (privileged). The per-user session
273 + // uses it to create the VM. Funneling VM creation through a factory lets the session
274 + // own when VMs are created, rather than having one handed to it up front.
275 + auto vmFactory = Microsoft::WRL::Make<WSLCVirtualMachineFactory>(Settings);
276
277 // Launch per-user COM server factory and add it to a fresh per-session job object for crash cleanup.
278 auto factory = wslutil::CreateComServerAsUser<IWSLCSessionFactory>(__uuidof(WSLCSessionFactory), userToken.get());
@@ -278,7 +281,7 @@ void WSLCSessionManagerImpl::CreateSession(
281 const auto sessionSettings = CreateSessionSettings(sessionId, callerFileName.c_str(), Settings, resolvedDisplayName.c_str());
282 wil::com_ptr<IWSLCSession> session;
283 wil::com_ptr<IWSLCSessionReference> serviceRef;
281 - THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), notifier.Get(), WarningCallback, &session, &serviceRef));
284 + THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vmFactory.Get(), notifier.Get(), WarningCallback, &session, &serviceRef));
285
286 // Track the session via its service ref, along with metadata and security info.
287 m_sessions.push_back(SessionEntry{
src/windows/service/inc/wslc.idl
+23 -3
@@ -533,6 +533,26 @@ interface IWSLCVirtualMachine : IUnknown
533 HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
534 }
535
536 +//
537 +// IWSLCVirtualMachineFactory - Creates VMs on demand for a session.
538 +//
539 +// Held by the per-user session process and implemented by the SYSTEM service.
540 +// This lets the session create a fresh VM at any time (e.g. to recreate a VM that
541 +// was idle-terminated when it had no running containers), instead of the service
542 +// eagerly creating a single VM up front. Each successful call returns a new VM whose
543 +// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down.
544 +//
545 +[
546 + uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B),
547 + pointer_default(unique),
548 + object
549 +]
550 +interface IWSLCVirtualMachineFactory : IUnknown
551 +{
552 + // Creates a new VM using the settings captured at session creation time.
553 + HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm);
554 +}
555 +
556 typedef enum _WSLCSessionStorageFlags
557 {
558 WSLCSessionStorageFlagsNone = 0,
@@ -803,10 +823,10 @@ interface IWSLCSession : IUnknown
823 // Returns a handle to this COM server process (used to add to job object).
824 HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle);
825
806 - // Initializes the session with a pre-created VM.
826 + // Initializes the session with a VM factory. VMs are created through the factory.
827 HRESULT Initialize(
828 [in] const WSLCSessionInitSettings* Settings,
809 - [in] IWSLCVirtualMachine* Vm,
829 + [in] IWSLCVirtualMachineFactory* VmFactory,
830 [in] IWSLCPluginNotifier* PluginNotifier,
831 [in, unique] IWarningCallback* WarningCallback);
832
@@ -863,7 +883,7 @@ interface IWSLCSessionFactory : IUnknown
883 // Creates a new session and returns both the session interface and a service reference.
884 HRESULT CreateSession(
885 [in] const WSLCSessionInitSettings* Settings,
866 - [in] IWSLCVirtualMachine* Vm,
886 + [in] IWSLCVirtualMachineFactory* VmFactory,
887 [in] IWSLCPluginNotifier* PluginNotifier,
888 [in, unique] IWarningCallback* WarningCallback,
889 [out] IWSLCSession** Session,
src/windows/wslcsession/WSLCSession.cpp
+12 -6
@@ -289,10 +289,13 @@ try
289 CATCH_RETURN();
290
291 HRESULT WSLCSession::Initialize(
292 - _In_ const WSLCSessionInitSettings* Settings, _In_ IWSLCVirtualMachine* Vm, _In_ IWSLCPluginNotifier* PluginNotifier, _In_opt_ IWarningCallback* WarningCallback)
292 + _In_ const WSLCSessionInitSettings* Settings,
293 + _In_ IWSLCVirtualMachineFactory* VmFactory,
294 + _In_ IWSLCPluginNotifier* PluginNotifier,
295 + _In_opt_ IWarningCallback* WarningCallback)
296 try
297 {
295 - RETURN_HR_IF(E_POINTER, Settings == nullptr || Vm == nullptr);
298 + RETURN_HR_IF(E_POINTER, Settings == nullptr || VmFactory == nullptr);
299 RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), m_virtualMachine.has_value());
300
301 THROW_HR_IF_MSG(
@@ -323,10 +326,13 @@ try
326 TraceLoggingValue(m_displayName.c_str(), "DisplayName"),
327 TraceLoggingValue(m_creatorProcessName.c_str(), "CreatorProcess"));
328
326 - // Create the VM. The VM produces crash events; the session multiplexes them out to any
327 - // registered ICrashDumpCallback subscribers via OnCrashDumpWritten.
329 + // Create the VM through the factory. The VM produces crash events; the session multiplexes
330 + // them out to any registered ICrashDumpCallback subscribers via OnCrashDumpWritten.
331 + wil::com_ptr<IWSLCVirtualMachine> vm;
332 + THROW_IF_FAILED(VmFactory->CreateVirtualMachine(&vm));
333 +
334 m_virtualMachine.emplace(
329 - Vm,
335 + vm.get(),
336 Settings,
337 m_sessionTerminatingEvent.get(),
338 std::bind(&WSLCSession::OnCrashDumpWritten, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
@@ -337,7 +343,7 @@ try
343 m_virtualMachine->Initialize();
344
345 // Get an event from the service that is signaled when the VM exits.
340 - THROW_IF_FAILED(Vm->GetTerminationEvent(&m_vmExitedEvent));
346 + THROW_IF_FAILED(vm->GetTerminationEvent(&m_vmExitedEvent));
347
348 // Configure storage.
349 ConfigureStorage(*Settings, tokenInfo->User.Sid);
src/windows/wslcsession/WSLCSession.h
+1 -1
@@ -92,7 +92,7 @@ public:
92 IFACEMETHOD(GetProcessHandle)(_Out_ HANDLE* ProcessHandle) override;
93 IFACEMETHOD(Initialize)(
94 _In_ const WSLCSessionInitSettings* Settings,
95 - _In_ IWSLCVirtualMachine* Vm,
95 + _In_ IWSLCVirtualMachineFactory* VmFactory,
96 _In_ IWSLCPluginNotifier* PluginNotifier,
97 _In_opt_ IWarningCallback* WarningCallback) override;
98
src/windows/wslcsession/WSLCSessionFactory.cpp
+3 -3
@@ -31,7 +31,7 @@ void wslc::WSLCSessionFactory::SetDestructionCallback(std::function<void()>&& ca
31
32 HRESULT wslc::WSLCSessionFactory::CreateSession(
33 _In_ const WSLCSessionInitSettings* Settings,
34 - _In_ IWSLCVirtualMachine* Vm,
34 + _In_ IWSLCVirtualMachineFactory* VmFactory,
35 _In_ IWSLCPluginNotifier* PluginNotifier,
36 _In_opt_ IWarningCallback* WarningCallback,
37 _Out_ IWSLCSession** Session,
@@ -51,8 +51,8 @@ try
51 // One session per process, so when it's destroyed, exit.
52 session->SetDestructionCallback(std::move(m_destructionCallback));
53
54 - // Initialize the session with the VM.
55 - RETURN_IF_FAILED(session->Initialize(Settings, Vm, PluginNotifier, WarningCallback));
54 + // Initialize the session with the VM factory (VMs are created on demand).
55 + RETURN_IF_FAILED(session->Initialize(Settings, VmFactory, PluginNotifier, WarningCallback));
56
57 // Create the service session ref. It extracts metadata and a weak reference from the session.
58 auto serviceRef = Microsoft::WRL::Make<wslc::WSLCSessionReference>(session.Get());
src/windows/wslcsession/WSLCSessionFactory.h
+1 -1
@@ -45,7 +45,7 @@ public:
45 // IWSLCSessionFactory
46 IFACEMETHOD(CreateSession)
47 (_In_ const WSLCSessionInitSettings* Settings,
48 - _In_ IWSLCVirtualMachine* Vm,
48 + _In_ IWSLCVirtualMachineFactory* VmFactory,
49 _In_ IWSLCPluginNotifier* PluginNotifier,
50 _In_opt_ IWarningCallback* WarningCallback,
51 _Out_ IWSLCSession** Session,