36
using wsl::windows::service::wslc::WSLCSession;
37
using wsl::windows::service::wslc::WSLCVirtualMachine;
38
39
-constexpr auto c_containerdStorage = "/var/lib/docker";
39
+constexpr auto c_containerdStorage = wsl::windows::wslc::ContainerdStorageMountPoint;
40
constexpr auto c_containerdSocket = "/run/containerd/containerd.sock";
41
-constexpr auto c_dockerdReadyLogLine = "API listen on /var/run/docker.sock";
41
constexpr auto c_storageVhdFilename = wsl::windows::wslc::DefaultStorageVhdName;
42
constexpr DWORD c_processTerminateTimeoutMs = 30 * 1000;
43
constexpr DWORD c_processKillTimeoutMs = 10 * 1000;
44
45
+// Default grace period to keep an otherwise-idle VM running before tearing it down (used when the
46
+// session's IdleTimeoutSec setting is 0/unset). This avoids thrashing the VM (repeated
47
+// teardown/recreate) when containers are created and destroyed, or operations issued, in quick
48
+// succession. The clock restarts whenever the VM is observed to be non-idle, so a full grace period
49
+// of continuous idleness is required before teardown.
50
+constexpr auto c_vmIdleGracePeriod = std::chrono::seconds(30);
51
+
52
namespace {
53
54
+// Validates the target path for a NEW session (one with no existing storage VHD): if the path
55
+// already exists it must be an empty directory, so session storage is never mixed with unrelated
56
+// user files. A non-existent path is fine (it will be created). Enforced eagerly at session
57
+// creation and again when the storage VHD is lazily created.
58
+void ValidateNewSessionStorageDirectory(const std::filesystem::path& StoragePath)
59
+{
60
+ // status's error_code distinguishes "doesn't exist yet" (OK, we'll create it) from other I/O errors.
61
+ std::error_code ec;
62
+ const auto status = std::filesystem::status(StoragePath, ec);
63
+ if (ec && ec.value() != ERROR_FILE_NOT_FOUND && ec.value() != ERROR_PATH_NOT_FOUND)
64
+ {
65
+ THROW_IF_WIN32_ERROR_MSG(ec.value(), "status failed for %ls", StoragePath.c_str());
66
+ }
67
+
68
+ if (!std::filesystem::exists(status))
69
+ {
70
+ return;
71
+ }
72
+
73
+ THROW_HR_WITH_USER_ERROR_IF(
74
+ E_INVALIDARG, Localization::MessageWslcSessionStorageMustBeDirectory(StoragePath.c_str()), !std::filesystem::is_directory(status));
75
+
76
+ const bool empty = std::filesystem::is_empty(StoragePath, ec);
77
+ THROW_IF_WIN32_ERROR_MSG(ec.value(), "is_empty failed for %ls", StoragePath.c_str());
78
+ THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcSessionStorageMustBeEmpty(StoragePath.c_str()), !empty);
79
+}
80
+
81
// Group policy: WSLContainerRegistryAllowlist restricts which container-image
82
// registries can be pulled from or pushed to. The check is enforced here at the
83
// service boundary so it covers ALL callers (wslc.exe CLI, the WslcSDK C API, and
364
try
365
{
366
RETURN_HR_IF(E_POINTER, Settings == nullptr || VmFactory == nullptr);
334
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), m_virtualMachine.has_value());
367
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED), m_vmFactoryGitCookie != 0);
368
369
THROW_HR_IF_MSG(
370
E_INVALIDARG, WI_IsAnyFlagSet(Settings->FeatureFlags, ~WSLCFeatureFlagsValid), "Invalid feature flags: 0x%x", Settings->FeatureFlags);
375
Settings->StorageFlags);
376
377
// Set up a warning context for the duration of initialization so that non-fatal
345
- // failures (e.g., container/volume/network recovery) are streamed to the CLI.
378
+ // failures are streamed to the CLI.
379
WSLCExecutionContext warningContext(this, WarningCallback);
380
381
+ // The VM (and storage VHD) is created lazily on the first operation. Validate the storage
382
+ // configuration eagerly here so misconfiguration is reported at session creation rather than
383
+ // surfacing later on the first VM-starting operation.
384
+ if (Settings->StoragePath != nullptr)
385
+ {
386
+ const std::filesystem::path storagePath{Settings->StoragePath};
387
+ THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessagePathNotAbsolute(Settings->StoragePath), !storagePath.is_absolute());
388
+
389
+ const auto vhdPath = storagePath / c_storageVhdFilename;
390
+ std::error_code existsError;
391
+ const bool vhdExists = std::filesystem::exists(vhdPath, existsError);
392
+ THROW_IF_WIN32_ERROR_MSG(existsError.value(), "exists failed for %ls", vhdPath.c_str());
393
+
394
+ if (WI_IsFlagSet(Settings->StorageFlags, WSLCSessionStorageFlagsNoCreate))
395
+ {
396
+ // The storage VHD must already exist (ConfigureStorage will not create it).
397
+ THROW_HR_WITH_USER_ERROR_IF(
398
+ HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), Localization::MessageWslcSessionStorageNotFound(Settings->StoragePath), !vhdExists);
399
+ }
400
+ else if (!vhdExists)
401
+ {
402
+ // New session: the target path (if it exists) must be an empty directory.
403
+ ValidateNewSessionStorageDirectory(storagePath);
404
+ }
405
+ }
406
+
407
// N.B. No locking is required because Initialize() is always called before the session is returned to the caller.
408
m_id = Settings->SessionId;
409
m_displayName = Settings->DisplayName ? Settings->DisplayName : L"";
411
m_featureFlags = Settings->FeatureFlags;
412
m_pluginNotifier = PluginNotifier;
413
355
- // Get user token for the current process
414
+ // Park the VM factory in the Global Interface Table. It is supplied here (on the call that
415
+ // creates the session) but used on demand from other threads/apartments; storing the raw
416
+ // proxy and calling it later would raise RPC_E_WRONG_THREAD.
417
+ m_git = wil::CoCreateInstance<IGlobalInterfaceTable>(CLSID_StdGlobalInterfaceTable, CLSCTX_INPROC_SERVER);
418
+ THROW_IF_FAILED(m_git->RegisterInterfaceInGlobal(VmFactory, __uuidof(IWSLCVirtualMachineFactory), &m_vmFactoryGitCookie));
419
+
420
+ // Persist a deep copy of the settings (and the creating user's SID) required to
421
+ // (re)create the VM on demand.
422
const auto tokenInfo = wil::get_token_information<TOKEN_USER>(GetCurrentProcessToken());
423
+ PersistSettings(*Settings, tokenInfo->User.Sid);
424
425
WSL_LOG(
426
"SessionInitialized",
428
TraceLoggingValue(m_displayName.c_str(), "DisplayName"),
429
TraceLoggingValue(m_creatorProcessName.c_str(), "CreatorProcess"));
430
364
- // Create the VM through the factory. The VM produces crash events; the session multiplexes
365
- // them out to any registered ICrashDumpCallback subscribers via OnCrashDumpWritten.
366
- wil::com_ptr<IWSLCVirtualMachine> vm;
367
- THROW_IF_FAILED(VmFactory->CreateVirtualMachine(&vm));
431
+ const auto idleGracePeriod = m_settings.IdleTimeoutSec > 0 ? std::chrono::seconds(m_settings.IdleTimeoutSec) : c_vmIdleGracePeriod;
432
369
- m_virtualMachine.emplace(
370
- vm.get(),
371
- Settings,
372
- m_sessionTerminatingEvent.get(),
373
- std::bind(&WSLCSession::OnCrashDumpWritten, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
433
+ WSLCSessionRuntime::RuntimeHooks hooks;
434
+ hooks.BringUp = [this]() {
435
+ // Configure storage.
436
+ ConfigureStorage(m_settings, m_userSid.empty() ? nullptr : reinterpret_cast<PSID>(m_userSid.data()));
437
375
- // Make sure that everything is destroyed correctly if an exception is thrown.
376
- auto errorCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_FAILED(Terminate()); });
438
+ // Mirror the host's trusted root CAs into the VM before dockerd starts.
439
+ InstallTrustedRootCertificates();
440
378
- m_virtualMachine->Initialize();
441
+ // Launch containerd first, then dockerd with the external containerd socket.
442
+ StartContainerd();
443
380
- // Get an event from the service that is signaled when the VM exits.
381
- THROW_IF_FAILED(vm->GetTerminationEvent(&m_vmExitedEvent));
444
+ // Reset the readiness event before (re)starting dockerd so a stale signal from a prior
445
+ // VM instance is not observed.
446
+ m_runtime.ResetDockerdReady();
447
+ StartDockerd();
448
383
- // Configure storage.
384
- ConfigureStorage(*Settings, tokenInfo->User.Sid);
449
+ m_runtime.InitializeDockerRuntime(m_storageVhdPath.parent_path());
450
+ };
451
386
- // Mirror the host's trusted root CAs into the VM before dockerd starts.
387
- InstallTrustedRootCertificates();
452
+ hooks.RecoverState = [this]() {
453
+ RecoverExistingNetworks();
454
+ RecoverExistingContainers();
455
+ };
456
389
- // Launch containerd first
390
- StartContainerd();
457
+ hooks.TearDownSessionState = [this](bool permanent) {
458
+ std::lock_guard containersLock(m_containersLock);
459
+ std::lock_guard networksLock(m_networksLock);
460
392
- // Launch dockerd with external containerd socket
393
- StartDockerd();
461
+ // Network metadata is rebuilt from dockerd on every VM start, so it is always dropped.
462
+ m_networks.clear();
463
395
- // Wait for dockerd to be ready before starting the event tracker.
396
- THROW_WIN32_IF_MSG(
397
- ERROR_TIMEOUT, !m_dockerdReadyEvent.wait(Settings->BootTimeoutMs), "Timed out waiting for dockerd to start");
464
+ // Container wrappers are kept alive across idle teardown (only cleared on permanent shutdown)
465
+ // so client COM references stay valid; RecoverState reattaches them to the restarted VM.
466
+ if (permanent)
467
+ {
468
+ m_containers.clear();
469
+ }
470
+ };
471
399
- auto [_, __, channel] = m_virtualMachine->Fork(WSLC_FORK::Thread);
472
+ hooks.OnSpontaneousExit = [this]() { LOG_IF_FAILED(Terminate()); };
473
401
- m_dockerClient.emplace(std::move(channel), m_virtualMachine->TerminatingEvent(), m_virtualMachine->VmId(), 10 * 1000);
474
+ // Forward VM start/stop to plugins. Both are best-effort: errors are logged and ignored so a
475
+ // misbehaving plugin cannot abort VM startup or the operation that triggered it.
476
+ hooks.OnVmStarted = [this]() {
477
+ if (m_pluginNotifier)
478
+ {
479
+ LOG_IF_FAILED(m_pluginNotifier->OnVmStarted());
480
+ }
481
+ };
482
403
- // Start the event tracker.
404
- m_eventTracker.emplace(m_dockerClient.value(), *this, m_ioRelay);
483
+ hooks.OnVmStopping = [this]() {
484
+ if (m_pluginNotifier)
485
+ {
486
+ LOG_IF_FAILED(m_pluginNotifier->OnVmStopping());
487
+ }
488
+ };
489
406
- m_volumes.emplace(m_dockerClient.value(), m_virtualMachine.value(), m_eventTracker.value(), m_storageVhdPath.parent_path());
490
+ hooks.OnCrashDump = std::bind(
491
+ &WSLCSession::OnCrashDumpWritten, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5);
492
408
- // Monitor for unexpected VM exit.
409
- m_ioRelay.AddHandle(std::make_unique<windows::common::io::EventHandle>(m_vmExitedEvent.get(), std::bind(&WSLCSession::OnVmExited, this)));
493
+ WSLCSessionRuntime::SessionContext sessionContext;
494
+ sessionContext.Id = m_id;
495
+ sessionContext.DisplayName = m_displayName;
496
+ sessionContext.Terminating = &m_terminating;
497
+ sessionContext.SessionTerminatingEvent = m_sessionTerminatingEvent;
498
+ sessionContext.SessionTerminatedEvent = m_sessionTerminatedEvent;
499
411
- // Recover any existing resources from storage.
412
- RecoverExistingNetworks();
413
- RecoverExistingContainers();
500
+ m_runtime.Initialize(m_vmFactoryGitCookie, m_git, &m_settings, idleGracePeriod, std::move(sessionContext), std::move(hooks));
501
415
- errorCleanup.release();
502
return S_OK;
503
}
504
CATCH_RETURN()
505
506
+void WSLCSession::PersistSettings(const WSLCSessionInitSettings& Settings, PSID UserSid)
507
+{
508
+ m_settings = Settings;
509
+
510
+ // Repoint the string fields at storage owned by the session so they outlive the caller's buffers.
511
+ m_settings.DisplayName = m_displayName.c_str();
512
+
513
+ if (Settings.CreatorProcessName != nullptr)
514
+ {
515
+ m_settingsCreatorProcessName = Settings.CreatorProcessName;
516
+ m_settings.CreatorProcessName = m_settingsCreatorProcessName->c_str();
517
+ }
518
+ else
519
+ {
520
+ m_settings.CreatorProcessName = nullptr;
521
+ }
522
+
523
+ if (Settings.StoragePath != nullptr)
524
+ {
525
+ m_settingsStoragePath = Settings.StoragePath;
526
+ m_settings.StoragePath = m_settingsStoragePath->c_str();
527
+ }
528
+ else
529
+ {
530
+ m_settings.StoragePath = nullptr;
531
+ }
532
+
533
+ if (Settings.RootVhdTypeOverride != nullptr)
534
+ {
535
+ m_settingsRootVhdTypeOverride = Settings.RootVhdTypeOverride;
536
+ m_settings.RootVhdTypeOverride = m_settingsRootVhdTypeOverride->c_str();
537
+ }
538
+ else
539
+ {
540
+ m_settings.RootVhdTypeOverride = nullptr;
541
+ }
542
+
543
+ THROW_HR_IF(E_UNEXPECTED, UserSid == nullptr);
544
+
545
+ const auto length = GetLengthSid(UserSid);
546
+ const auto* bytes = reinterpret_cast<const BYTE*>(UserSid);
547
+ m_userSid.assign(bytes, bytes + length);
548
+}
549
+
550
+WSLCSession::VmLease WSLCSession::AcquireLease(WSLCSessionRuntime::VmLeasePolicy Policy)
551
+{
552
+ return m_runtime.AcquireVmLease(Policy);
553
+}
554
+
555
WSLCSession::~WSLCSession()
556
{
557
WSL_LOG("SessionTerminated", TraceLoggingValue(m_id, "SessionId"), TraceLoggingValue(m_displayName.c_str(), "DisplayName"));
574
if (Settings.StoragePath == nullptr)
575
{
576
// If no storage path is specified, use a tmpfs for convenience.
442
- m_virtualMachine->Mount("", c_containerdStorage, "tmpfs", "", 0);
443
- m_storageMounted = true;
577
+ m_runtime.Vm().Mount("", c_containerdStorage, "tmpfs", "", 0);
578
+ m_runtime.SetStorageMounted(true);
579
return;
580
}
581
593
{
594
if (diskLun.has_value())
595
{
461
- m_virtualMachine->DetachDisk(diskLun.value());
596
+ m_runtime.Vm().DetachDisk(diskLun.value());
597
}
598
599
LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_storageVhdPath.c_str()));
601
});
602
603
auto result =
469
- wil::ResultFromException([&]() { diskDevice = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false).second; });
604
+ wil::ResultFromException([&]() { diskDevice = m_runtime.Vm().AttachDisk(m_storageVhdPath.c_str(), false).second; });
605
606
if (FAILED(result))
607
{
618
WI_IsFlagSet(Settings.StorageFlags, WSLCSessionStorageFlagsNoCreate));
619
620
// Reject any non-empty existing path so we don't mix user files with session storage.
486
- // status's error_code distinguishes "doesn't exist yet" (OK, we'll create it) from other I/O errors.
487
- std::error_code ec;
488
- const auto status = std::filesystem::status(storagePath, ec);
489
- if (ec && ec.value() != ERROR_FILE_NOT_FOUND && ec.value() != ERROR_PATH_NOT_FOUND)
490
- {
491
- THROW_IF_WIN32_ERROR_MSG(ec.value(), "status failed for %ls", storagePath.c_str());
492
- }
493
-
494
- if (std::filesystem::exists(status))
495
- {
496
- THROW_HR_WITH_USER_ERROR_IF(
497
- E_INVALIDARG, Localization::MessageWslcSessionStorageMustBeDirectory(storagePath.c_str()), !std::filesystem::is_directory(status));
498
-
499
- const bool empty = std::filesystem::is_empty(storagePath, ec);
500
- THROW_IF_WIN32_ERROR_MSG(ec.value(), "is_empty failed for %ls", storagePath.c_str());
501
- THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessageWslcSessionStorageMustBeEmpty(storagePath.c_str()), !empty);
502
- }
621
+ ValidateNewSessionStorageDirectory(storagePath);
622
623
// If the VHD wasn't found, create it.
624
WSL_LOG("CreateStorageVhd", TraceLoggingValue(m_storageVhdPath.c_str(), "StorageVhdPath"));
633
vhdCreated = true;
634
635
// Then attach the new disk.
517
- std::tie(diskLun, diskDevice) = m_virtualMachine->AttachDisk(m_storageVhdPath.c_str(), false);
636
+ std::tie(diskLun, diskDevice) = m_runtime.Vm().AttachDisk(m_storageVhdPath.c_str(), false);
637
638
// Then format it.
520
- m_virtualMachine->Ext4Format(diskDevice);
639
+ m_runtime.Vm().Ext4Format(diskDevice);
640
}
641
642
// Mount the device to /root.
524
- m_virtualMachine->Mount(diskDevice.c_str(), c_containerdStorage, "ext4", "", 0);
525
- m_storageMounted = true;
643
+ m_runtime.Vm().Mount(diskDevice.c_str(), c_containerdStorage, "ext4", "", 0);
644
+ m_runtime.SetStorageMounted(true);
645
646
// Configure swap on a separate ephemeral VHD.
647
if (Settings.SwapSizeMb > 0)
648
{
649
try
650
{
532
- m_swapVhdPath = storagePath / "swap.vhdx";
533
- DeleteFileW(m_swapVhdPath.c_str()); // Remove stale swap from prior run
534
- wsl::core::filesystem::CreateVhd(m_swapVhdPath.c_str(), static_cast<ULONGLONG>(Settings.SwapSizeMb) * _1MB, UserSid, false, false);
651
+ std::filesystem::path swapVhdPath = storagePath / "swap.vhdx";
652
+ m_runtime.SetSwapVhdPath(swapVhdPath);
653
+ DeleteFileW(swapVhdPath.c_str()); // Remove stale swap from prior run
654
+ wsl::core::filesystem::CreateVhd(swapVhdPath.c_str(), static_cast<ULONGLONG>(Settings.SwapSizeMb) * _1MB, UserSid, false, false);
655
536
- auto [_, swapDevice] = m_virtualMachine->AttachDisk(m_swapVhdPath.c_str(), false);
656
+ auto [_, swapDevice] = m_runtime.Vm().AttachDisk(swapVhdPath.c_str(), false);
657
658
// Fire-and-forget: mkswap + swapon runs asynchronously since swap is best-effort.
659
auto cmd = std::format("/usr/sbin/mkswap {0} && /usr/sbin/swapon {0}", swapDevice);
660
ServiceProcessLauncher launcher("/bin/sh", {"/bin/sh", "-c", cmd});
541
- launcher.Launch(*m_virtualMachine);
661
+ launcher.Launch(m_runtime.Vm());
662
}
663
catch (...)
664
{
692
693
void WSLCSession::OnDockerdExited()
694
{
575
- if (!m_sessionTerminatingEvent.is_signaled())
695
+ if (!m_sessionTerminatingEvent.is_signaled() && m_runtime.ExitDisposition() != WSLCSessionRuntime::VmExitDisposition::StopRequested)
696
{
697
WSL_LOG("UnexpectedDockerdExit", TraceLoggingValue(m_displayName.c_str(), "Name"));
698
}
700
701
void WSLCSession::OnContainerdExited()
702
{
583
- if (!m_sessionTerminatingEvent.is_signaled())
703
+ if (!m_sessionTerminatingEvent.is_signaled() && m_runtime.ExitDisposition() != WSLCSessionRuntime::VmExitDisposition::StopRequested)
704
{
705
WSL_LOG("UnexpectedContainerdExit", TraceLoggingValue(m_displayName.c_str(), "Name"));
706
}
707
}
708
589
-void WSLCSession::OnVmExited()
590
-{
591
- WSL_LOG(
592
- "VmExited",
593
- TraceLoggingLevel(WINEVENT_LEVEL_WARNING),
594
- TraceLoggingValue(m_id, "SessionId"),
595
- TraceLoggingValue(m_displayName.c_str(), "Name"),
596
- TraceLoggingValue(!m_sessionTerminatingEvent.is_signaled(), "Unexpected"));
597
-
598
- LOG_IF_FAILED(Terminate());
599
-}
600
-
601
-void WSLCSession::OnProcessLog(const gsl::span<char>& Buffer, PCSTR Source)
602
-try
603
-{
604
- if (Buffer.empty())
605
- {
606
- return;
607
- }
608
-
609
- std::string entry = {Buffer.begin(), Buffer.end()};
610
- WSL_LOG(
611
- "ContainerdLog",
612
- TraceLoggingValue(Source, "Source"),
613
- TraceLoggingValue(entry.c_str(), "Content"),
614
- TraceLoggingValue(m_displayName.c_str(), "Name"));
615
-
616
- if (!m_dockerdReadyEvent.is_signaled())
617
- {
618
- if (entry.find(c_dockerdReadyLogLine) != std::string::npos)
619
- {
620
- m_dockerdReadyEvent.SetEvent();
621
- }
622
- }
623
-}
624
-CATCH_LOG();
625
-
709
ServiceRunningProcess WSLCSession::StartProcess(
710
const std::string& Executable, const std::vector<std::string>& Args, PCSTR LogSource, std::function<void()>&& ExitCallback)
711
{
712
ServiceProcessLauncher launcher{Executable, Args, {{"PATH=/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin"}}};
713
631
- auto process = launcher.Launch(*m_virtualMachine);
714
+ auto process = launcher.Launch(m_runtime.Vm());
715
633
- m_ioRelay.AddHandle(std::make_unique<windows::common::io::LineBasedReadHandle>(
634
- process.GetStdHandle(1), [this, LogSource](const auto& data) { OnProcessLog(data, LogSource); }, false));
716
+ m_runtime.Relay()->AddHandle(std::make_unique<windows::common::io::LineBasedReadHandle>(
717
+ process.GetStdHandle(1), [this, LogSource](const auto& data) { m_runtime.OnProcessLog(data, LogSource); }, false));
718
636
- m_ioRelay.AddHandle(std::make_unique<windows::common::io::LineBasedReadHandle>(
637
- process.GetStdHandle(2), [this, LogSource](const auto& data) { OnProcessLog(data, LogSource); }, false));
719
+ m_runtime.Relay()->AddHandle(std::make_unique<windows::common::io::LineBasedReadHandle>(
720
+ process.GetStdHandle(2), [this, LogSource](const auto& data) { m_runtime.OnProcessLog(data, LogSource); }, false));
721
639
- m_ioRelay.AddHandle(std::make_unique<windows::common::io::EventHandle>(process.GetExitEvent(), std::move(ExitCallback)));
722
+ m_runtime.Relay()->AddHandle(std::make_unique<windows::common::io::EventHandle>(process.GetExitEvent(), std::move(ExitCallback)));
723
724
return process;
725
}
737
args.emplace_back("debug");
738
}
739
657
- m_containerdProcess = StartProcess("/usr/bin/containerd", args, "containerd", std::bind(&WSLCSession::OnContainerdExited, this));
740
+ m_runtime.SetContainerdProcess(StartProcess("/usr/bin/containerd", args, "containerd", std::bind(&WSLCSession::OnContainerdExited, this)));
741
WSL_LOG("ContainerdStarted");
742
}
743
750
args.emplace_back("--debug");
751
}
752
670
- m_dockerdProcess = StartProcess("/usr/bin/dockerd", args, "dockerd", std::bind(&WSLCSession::OnDockerdExited, this));
753
+ m_runtime.SetDockerdProcess(StartProcess("/usr/bin/dockerd", args, "dockerd", std::bind(&WSLCSession::OnDockerdExited, this)));
754
WSL_LOG("DockerdStarted");
755
}
756
770
const auto script = std::format("cat > '{}'", c_certPath);
771
772
ServiceProcessLauncher launcher("/bin/sh", {"/bin/sh", "--norc", "-c", script}, {}, WSLCProcessFlagsStdin);
690
- auto process = launcher.Launch(*m_virtualMachine);
773
+ auto process = launcher.Launch(m_runtime.Vm());
774
775
std::unique_ptr<OverlappedIOHandle> writeStdin(
776
new WriteHandle(process.GetStdHandle(WSLCFDStdin), std::vector<char>{pem.begin(), pem.end()}));
929
auto tagOrDigest = reference.TagOrDigest();
930
EnforceRegistryAllowlist(repo);
931
849
- auto lock = m_lock.lock_shared();
850
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
932
+ auto runtime = m_runtime.Acquire();
933
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
934
935
if (!tagOrDigest.has_value())
936
{
944
registryAuth = std::string(RegistryAuthenticationInformation);
945
}
946
864
- auto requestContext = m_dockerClient->PullImage(repo.Name, tagOrDigest, registryAuth);
947
+ auto requestContext = runtime.Docker().PullImage(repo.Name, tagOrDigest, registryAuth);
948
StreamImageOperation(*requestContext, Image, "Pull", ProgressCallback);
949
950
OnImageCreated(Image);
988
comCall = RegisterUserCOMCallback();
989
}
990
908
- auto lock = m_lock.lock_shared();
991
+ auto runtime = m_runtime.Acquire();
992
910
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
993
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
994
995
// Track every Windows folder we mount into the VM during this build so a single scope_exit
996
// unmounts them all on success or on any throw partway through the loop below.
1000
{
1001
// Best-effort but not silent: a failed unmount can leave a file-secret share mounted in the
1002
// guest, so log it. Never throw here.
920
- LOG_IF_FAILED(m_virtualMachine->UnmountWindowsFolder(path.c_str()));
1003
+ LOG_IF_FAILED(runtime.Vm().UnmountWindowsFolder(path.c_str()));
1004
}
1005
});
1006
auto mountInVm = [&](LPCWSTR windowsPath, BOOL readOnly, std::string_view guestBase = "/mnt") -> std::string {
1007
GUID id{};
1008
THROW_IF_FAILED(CoCreateGuid(&id));
1009
auto vmPath = std::format("{}/{}", guestBase, wsl::shared::string::GuidToString<char>(id));
927
- THROW_IF_FAILED(m_virtualMachine->MountWindowsFolder(windowsPath, vmPath.c_str(), readOnly));
1010
+ THROW_IF_FAILED(runtime.Vm().MountWindowsFolder(windowsPath, vmPath.c_str(), readOnly));
1011
mountedPaths.push_back(std::move(vmPath));
1012
return mountedPaths.back();
1013
};
1202
WSL_LOG("BuildImageStart", TraceLoggingValue(wsl::shared::string::Join(buildArgs, ' ').c_str(), "Command"));
1203
1204
ServiceProcessLauncher buildLauncher(buildArgs[0], buildArgs, buildEnv, WSLCProcessFlagsStdin);
1122
- auto buildProcess = buildLauncher.Launch(*m_virtualMachine);
1205
+ auto buildProcess = buildLauncher.Launch(runtime.Vm());
1206
1207
// Opened before the IO context so it outlives the relay registered on it below.
1208
std::optional<UserHandle> userHandle;
1496
{
1497
WSLCExecutionContext context(this, WarningCallback);
1498
1416
- auto lock = m_lock.lock_shared();
1499
+ auto lock = AcquireLease();
1500
1418
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1501
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1502
1420
- auto requestContext = m_dockerClient->LoadImage(ContentSize);
1503
+ auto requestContext = m_runtime.Docker().LoadImage(ContentSize);
1504
1505
std::ignore = ImportImageImpl(*requestContext, ImageHandle, LoadCallback);
1506
1530
tag = tagOrDigest.value();
1531
}
1532
1450
- auto lock = m_lock.lock_shared();
1533
+ auto lock = AcquireLease();
1534
1452
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1535
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1536
1454
- auto requestContext = m_dockerClient->ImportImage(repo, tag, ContentSize);
1537
+ auto requestContext = m_runtime.Docker().ImportImage(repo, tag, ContentSize);
1538
1539
auto imageId = ImportImageImpl(*requestContext, ImageHandle);
1540
THROW_HR_IF_MSG(E_UNEXPECTED, !imageId.has_value(), "Docker import succeeded but did not return an image ID");
1564
comCall = RegisterUserCOMCallback();
1565
}
1566
1484
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1567
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1568
1569
auto io = CreateIOContext();
1570
1701
1702
RETURN_HR_IF_NULL(E_POINTER, ImageNameOrID);
1703
RETURN_HR_IF(E_INVALIDARG, strlen(ImageNameOrID) > WSLC_MAX_IMAGE_NAME_LENGTH);
1621
- auto lock = m_lock.lock_shared();
1704
+ auto lock = AcquireLease();
1705
1623
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1706
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1707
1625
- auto retVal = m_dockerClient->SaveImage(ImageNameOrID);
1708
+ auto retVal = m_runtime.Docker().SaveImage(ImageNameOrID);
1709
SaveImageImpl(retVal, OutHandle, CancelEvent);
1710
return S_OK;
1711
}
1734
names.emplace_back(ImageNames->Values[i]);
1735
}
1736
1654
- auto lock = m_lock.lock_shared();
1737
+ auto lock = AcquireLease();
1738
1656
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1739
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1740
1658
- auto retVal = m_dockerClient->SaveImages(names);
1741
+ auto retVal = m_runtime.Docker().SaveImages(names);
1742
SaveImageImpl(retVal, OutHandle, CancelEvent);
1743
return S_OK;
1744
}
1748
{
1749
auto userHandle = OpenUserHandle(OutputHandle);
1750
1668
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1751
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1752
1753
auto io = CreateIOContext(CancelEvent);
1754
1810
filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Options->Filters, Options->FiltersCount);
1811
}
1812
1730
- auto lock = m_lock.lock_shared();
1813
+ auto lock = AcquireLease();
1814
1732
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1815
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1816
1817
std::vector<docker_schema::Image> images;
1818
try
1819
{
1737
- images = m_dockerClient->ListImages(all, digests, filters);
1820
+ images = m_runtime.Docker().ListImages(all, digests, filters);
1821
}
1822
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to list images");
1823
1919
*DeletedImages = nullptr;
1920
*Count = 0;
1921
1839
- auto lock = m_lock.lock_shared();
1922
+ auto lock = AcquireLease();
1923
1841
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1924
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1925
1926
std::vector<docker_schema::DeletedImage> deletedImages;
1927
try
1928
{
1846
- deletedImages = m_dockerClient->DeleteImage(
1929
+ deletedImages = m_runtime.Docker().DeleteImage(
1930
Options->Image, WI_IsFlagSet(Options->Flags, WSLCDeleteImageFlagsForce), WI_IsFlagSet(Options->Flags, WSLCDeleteImageFlagsNoPrune));
1931
}
1932
catch (const DockerHTTPException& e)
1993
RETURN_HR_IF_NULL(E_POINTER, Options->Tag);
1994
RETURN_HR_IF(E_INVALIDARG, strlen(Options->Repo) + strlen(Options->Tag) + 1 > WSLC_MAX_IMAGE_NAME_LENGTH);
1995
1913
- auto lock = m_lock.lock_shared();
1996
+ auto lock = AcquireLease();
1997
1915
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
1998
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
1999
2000
try
2001
{
1919
- m_dockerClient->TagImage(Options->Image, Options->Repo, Options->Tag);
2002
+ m_runtime.Docker().TagImage(Options->Image, Options->Repo, Options->Tag);
2003
}
2004
catch (const DockerHTTPException& e)
2005
{
2032
auto tagOrDigest = reference.TagOrDigest();
2033
EnforceRegistryAllowlist(repo);
2034
1952
- auto lock = m_lock.lock_shared();
1953
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2035
+ auto lock = AcquireLease();
2036
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2037
1955
- auto requestContext = m_dockerClient->PushImage(repo.Name, tagOrDigest, RegistryAuthenticationInformation);
2038
+ auto requestContext = m_runtime.Docker().PushImage(repo.Name, tagOrDigest, RegistryAuthenticationInformation);
2039
StreamImageOperation(*requestContext, Image, "Push", ProgressCallback);
2040
2041
return S_OK;
2053
2054
*Output = nullptr;
2055
1973
- auto lock = m_lock.lock_shared();
1974
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2056
+ auto lock = AcquireLease();
2057
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2058
2059
*Output = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(InspectImageLockHeld(ImageNameOrId).c_str()).release();
2060
2067
docker_schema::InspectImage dockerInspect;
2068
try
2069
{
1987
- dockerInspect = m_dockerClient->InspectImage(NameOrId);
2070
+ dockerInspect = m_runtime.Docker().InspectImage(NameOrId);
2071
}
2072
catch (const DockerHTTPException& e)
2073
{
2101
2102
*IdentityToken = nullptr;
2103
2021
- auto lock = m_lock.lock_shared();
2022
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2104
+ auto lock = AcquireLease();
2105
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2106
2107
wil::unique_cotaskmem_ansistring token;
2108
2109
try
2110
{
2028
- auto response = m_dockerClient->Authenticate(ServerAddress, Username, Password);
2111
+ auto response = m_runtime.Docker().Authenticate(ServerAddress, Username, Password);
2112
token = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(response.c_str());
2113
}
2114
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to authenticate with registry: %hs", ServerAddress);
2133
2134
auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
2135
2053
- auto lock = m_lock.lock_shared();
2054
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2136
+ auto lock = AcquireLease();
2137
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2138
2139
docker_schema::PruneImageResult pruneResult;
2140
try
2141
{
2059
- pruneResult = m_dockerClient->PruneImages(filters);
2142
+ pruneResult = m_runtime.Docker().PruneImages(filters);
2143
}
2144
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to prune images");
2145
2194
"Invalid process flags: 0x%x",
2195
containerOptions->InitProcessOptions.Flags);
2196
2114
- auto lock = m_lock.lock_shared();
2197
+ auto lock = AcquireLease();
2198
2199
auto result = wil::ResultFromException([&]() { CreateContainerImpl(containerOptions, Container); });
2200
2215
2216
void WSLCSession::CreateContainerImpl(const WSLCContainerOptions* containerOptions, IWSLCContainer** Container)
2217
{
2135
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
2136
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_eventTracker);
2137
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient);
2138
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2218
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
2219
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasEvents());
2220
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2221
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2222
2223
// Validate that name & images are valid.
2224
if (containerOptions->Name != nullptr && containerOptions->Name[0] != '\0')
2265
*containerOptions,
2266
containerName,
2267
*this,
2185
- m_virtualMachine.value(),
2268
+ m_runtime,
2269
m_pluginNotifier.get(),
2270
m_networks,
2188
- m_volumes.value(),
2189
- std::bind(&WSLCSession::OnContainerDeleted, this, std::placeholders::_1),
2190
- m_eventTracker.value(),
2191
- m_dockerClient.value(),
2192
- m_ioRelay);
2271
+ std::bind(&WSLCSession::OnContainerDeleted, this, std::placeholders::_1));
2272
2273
// Key the map by Docker's container ID, which is set in the WSLCContainerImpl constructor and stable for its lifetime.
2274
auto [it, inserted] = m_containers.emplace(container->ID(), std::move(container));
2301
ValidateName(Id, WSLC_MAX_CONTAINER_NAME_LENGTH);
2302
2303
// Look for an exact ID match first.
2225
- auto lock = m_lock.lock_shared();
2304
+ auto lock = AcquireLease();
2305
std::lock_guard containersLock{m_containersLock};
2306
2307
// Purge containers that were auto-deleted via OnEvent (--rm).
2316
2317
try
2318
{
2240
- inspectResult = m_dockerClient->InspectContainer(Id);
2319
+ inspectResult = m_runtime.Docker().InspectContainer(Id);
2320
}
2321
catch (DockerHTTPException& e)
2322
{
2340
}
2341
CATCH_RETURN();
2342
2343
+namespace {
2344
+
2345
+ // Activity token holds an activity reference to prevent idle VM teardown while client holds it.
2346
+ // Implements IFastRundown so crashed clients reclaim stub promptly instead of slow default rundown.
2347
+ class ContainerOperation
2348
+ : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IUnknown, IFastRundown>
2349
+ {
2350
+ public:
2351
+ // Adopts an activity reference from CreateActivityToken; callback releases it.
2352
+ void Initialize(std::function<void()>&& onRelease) noexcept
2353
+ {
2354
+ m_onRelease = std::move(onRelease);
2355
+ }
2356
+
2357
+ ~ContainerOperation() override
2358
+ {
2359
+ if (m_onRelease)
2360
+ {
2361
+ m_onRelease();
2362
+ }
2363
+ }
2364
+
2365
+ private:
2366
+ std::function<void()> m_onRelease;
2367
+ };
2368
+
2369
+} // namespace
2370
+
2371
+Microsoft::WRL::ComPtr<IUnknown> WSLCSession::CreateActivityToken()
2372
+{
2373
+ // Record the in-flight activity up front so the VM cannot idle-terminate before the caller
2374
+ // takes ownership of the returned token.
2375
+ m_runtime.Idle().AddActivity();
2376
+ auto countCleanup = wil::scope_exit([this]() { m_runtime.Idle().ReleaseActivity(); });
2377
+
2378
+ auto operation = Microsoft::WRL::Make<ContainerOperation>();
2379
+ THROW_IF_NULL_ALLOC(operation.Get());
2380
+
2381
+ // Capture shared idle state so token can outlive session and release activity without keeping session alive.
2382
+ std::shared_ptr<IdleState> idleState = m_runtime.IdleStateShared();
2383
+ operation->Initialize([idleState = std::move(idleState)]() { idleState->ReleaseActivity(); });
2384
+
2385
+ // The token now owns the activity-count reference and will release it on destruction.
2386
+ countCleanup.release();
2387
+
2388
+ Microsoft::WRL::ComPtr<IUnknown> token;
2389
+ THROW_IF_FAILED(operation.As(&token));
2390
+ return token;
2391
+}
2392
+
2393
+HRESULT WSLCSession::BeginContainerOperation(IUnknown** Operation)
2394
+try
2395
+{
2396
+ WSLCExecutionContext context(this);
2397
+
2398
+ RETURN_HR_IF_NULL(E_POINTER, Operation);
2399
+ *Operation = nullptr;
2400
+
2401
+ // Do not start a new operation (which would hold the VM alive) once the session is terminating
2402
+ // or has terminated. Mirrors the gate in EnsureVmRunning().
2403
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_terminating.load() || m_sessionTerminatedEvent.is_signaled());
2404
+
2405
+ // Record the in-flight operation up front so the VM cannot idle-terminate before the client
2406
+ // resolves the container and issues the operation (and streams any output).
2407
+ auto token = CreateActivityToken();
2408
+
2409
+ RETURN_IF_FAILED(token.CopyTo(Operation));
2410
+ return S_OK;
2411
+}
2412
+CATCH_RETURN();
2413
+
2414
HRESULT WSLCSession::ListContainers(
2415
const WSLCListContainersOptions* Options, WSLCContainerEntry** Containers, ULONG* Count, WSLCContainerPortMapping** Ports, ULONG* PortsCount)
2416
try
2445
filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Options->Filters, Options->FiltersCount);
2446
}
2447
2298
- auto lock = m_lock.lock_shared();
2299
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2448
+ auto lock = AcquireLease();
2449
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2450
2451
std::vector<docker_schema::ContainerInfo> dockerContainers;
2452
try
2453
{
2304
- dockerContainers = m_dockerClient->ListContainers(all, limit, filters);
2454
+ dockerContainers = m_runtime.Docker().ListContainers(all, limit, filters);
2455
}
2456
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to list containers");
2457
2524
2525
auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
2526
2377
- auto lock = m_lock.lock_shared();
2378
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient.has_value());
2527
+ auto lock = AcquireLease();
2528
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2529
2530
std::lock_guard containersLock{m_containersLock};
2531
2533
2534
try
2535
{
2386
- pruneResult = m_dockerClient->PruneContainers(filters);
2536
+ pruneResult = m_runtime.Docker().PruneContainers(filters);
2537
}
2538
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to prune containers");
2539
2580
CATCH_RETURN();
2581
2582
HRESULT WSLCSession::CreateRootNamespaceProcess(
2433
- LPCSTR Executable, const WSLCProcessOptions* Options, ULONG TtyRows, ULONG TtyColumns, IWSLCProcess** Process, int* Errno)
2583
+ LPCSTR Executable, const WSLCProcessOptions* Options, ULONG TtyRows, ULONG TtyColumns, BOOL AcquireVmLease, IWSLCProcess** Process, int* Errno)
2584
try
2585
{
2586
WSLCExecutionContext context(this);
2595
*Errno = -1; // Make sure not to return 0 if something fails.
2596
}
2597
2448
- auto lock = m_lock.lock_shared();
2449
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
2598
+ auto runtime = m_runtime.Acquire(LeasePolicyFor(AcquireVmLease));
2599
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
2600
+
2601
+ auto process = runtime.Vm().CreateLinuxProcess(Executable, *Options, TtyRows, TtyColumns, Errno);
2602
+
2603
+ // The VmLease above is released when this call returns, but the process keeps running in the
2604
+ // VM and the client holds the returned proxy. A root-namespace process is not tracked as a
2605
+ // container, so attach an activity token bound to the process's lifetime; this keeps the VM
2606
+ // alive for as long as the client holds the process, preventing the idle worker from tearing
2607
+ // the VM down and killing the process out from under the client.
2608
+ //
2609
+ // Not for a plugin-originated call: it was served by whatever VM was already running, possibly
2610
+ // one already committed to stopping, and a plugin must never extend a VM's life. Attaching a
2611
+ // token anyway would keep counting activity for as long as the plugin holds the proxy and would
2612
+ // block idle termination of every subsequent VM in this session.
2613
+ if (AcquireVmLease)
2614
+ {
2615
+ process->SetKeepAliveToken(CreateActivityToken());
2616
+ }
2617
2451
- auto process = m_virtualMachine->CreateLinuxProcess(Executable, *Options, TtyRows, TtyColumns, Errno);
2618
THROW_IF_FAILED(process.CopyTo(Process));
2619
2620
return S_OK;
2625
{
2626
constexpr auto mkfsPath = "/usr/sbin/mkfs.ext4";
2627
ServiceProcessLauncher launcher(mkfsPath, {mkfsPath, Device});
2462
- auto result = launcher.Launch(*m_virtualMachine).WaitAndCaptureOutput();
2628
+ auto result = launcher.Launch(m_runtime.Vm()).WaitAndCaptureOutput();
2629
2630
THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str());
2631
}
2637
2638
THROW_HR_WITH_USER_ERROR_IF(E_INVALIDARG, Localization::MessagePathNotAbsolute(Path), !std::filesystem::path(Path).is_absolute());
2639
2474
- auto lock = m_lock.lock_shared();
2475
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
2640
+ auto lock = AcquireLease();
2641
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
2642
2643
// Attach the disk to the VM (AttachDisk() performs the access check for the VHD file).
2478
- auto [lun, device] = m_virtualMachine->AttachDisk(Path, false);
2644
+ auto [lun, device] = m_runtime.Vm().AttachDisk(Path, false);
2645
2646
// N.B. DetachDisk calls sync() before detaching.
2481
- auto detachDisk = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, lun]() { m_virtualMachine->DetachDisk(lun); });
2647
+ auto detachDisk = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, lun]() { m_runtime.Vm().DetachDisk(lun); });
2648
2649
// Format it to ext4.
2484
- m_virtualMachine->Ext4Format(device);
2650
+ m_runtime.Vm().Ext4Format(device);
2651
2652
return S_OK;
2653
}
2665
auto driverOpts = wslutil::ParseKeyValuePairs(Options->DriverOpts, Options->DriverOptsCount);
2666
auto labels = wslutil::ParseKeyValuePairs(Options->Labels, Options->LabelsCount, WSLCVolumeMetadataLabel);
2667
2502
- auto lock = m_lock.lock_shared();
2503
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2668
+ auto lock = AcquireLease();
2669
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2670
2671
if (Options->Name != nullptr && Options->Name[0] != '\0')
2672
{
2673
ValidateName(Options->Name, WSLC_MAX_VOLUME_NAME_LENGTH);
2674
}
2675
2510
- *VolumeInfo = m_volumes->CreateVolume(Options->Name, Options->Driver, std::move(driverOpts), std::move(labels));
2676
+ *VolumeInfo = m_runtime.Volumes().CreateVolume(Options->Name, Options->Driver, std::move(driverOpts), std::move(labels));
2677
return S_OK;
2678
}
2679
CATCH_RETURN();
2685
2686
RETURN_HR_IF_NULL(E_POINTER, Name);
2687
2522
- auto lock = m_lock.lock_shared();
2523
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2688
+ auto lock = AcquireLease();
2689
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2690
2525
- m_volumes->DeleteVolume(Name);
2691
+ m_runtime.Volumes().DeleteVolume(Name);
2692
return S_OK;
2693
}
2694
CATCH_RETURN();
2706
2707
auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
2708
2543
- auto lock = m_lock.lock_shared();
2544
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2709
+ auto lock = AcquireLease();
2710
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2711
2546
- auto volumeList = m_volumes->ListVolumes(std::move(filters));
2712
+ auto volumeList = m_runtime.Volumes().ListVolumes(std::move(filters));
2713
2714
if (volumeList.empty())
2715
{
2738
std::string name = Name;
2739
ValidateName(name.c_str(), WSLC_MAX_VOLUME_NAME_LENGTH);
2740
2575
- auto lock = m_lock.lock_shared();
2576
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2741
+ auto lock = AcquireLease();
2742
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2743
2578
- std::string json = m_volumes->InspectVolume(name);
2744
+ std::string json = m_runtime.Volumes().InspectVolume(name);
2745
*Output = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(json.c_str()).release();
2746
2747
return S_OK;
2763
2764
auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
2765
2600
- auto lock = m_lock.lock_shared();
2601
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_volumes);
2766
+ auto lock = AcquireLease();
2767
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVolumes());
2768
2769
WSLCVolumes::PruneVolumesResult pruneResult;
2770
try
2771
{
2606
- pruneResult = m_volumes->PruneVolumes(filters);
2772
+ pruneResult = m_runtime.Volumes().PruneVolumes(filters);
2773
}
2774
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to prune volumes");
2775
2795
}
2796
CATCH_RETURN();
2797
2632
-int WSLCSession::StopProcess(ServiceRunningProcess& Process, DWORD TerminateTimeoutMs, DWORD KillTimeoutMs)
2633
-{
2634
- auto signalResult = Process.Get().Signal(WSLCSignalSIGTERM);
2635
- if (FAILED(signalResult))
2636
- {
2637
- LOG_HR_MSG(signalResult, "Failed to terminate process %i", Process.Get().GetPid());
2638
- return -1;
2639
- }
2640
-
2641
- try
2642
- {
2643
- return Process.Wait(TerminateTimeoutMs);
2644
- }
2645
- catch (...)
2646
- {
2647
- LOG_CAUGHT_EXCEPTION();
2648
- try
2649
- {
2650
- LOG_IF_FAILED(Process.Get().Signal(WSLCSignalSIGKILL));
2651
- return Process.Wait(KillTimeoutMs);
2652
- }
2653
- CATCH_LOG();
2654
- }
2655
-
2656
- return -1;
2657
-}
2798
// Network management.
2799
2800
HRESULT WSLCSession::CreateNetwork(const WSLCNetworkOptions* Options, IWarningCallback* WarningCallback)
2816
auto driverOpts = wslutil::ParseKeyValuePairs(Options->DriverOpts, Options->DriverOptsCount);
2817
auto labels = wslutil::ParseKeyValuePairs(Options->Labels, Options->LabelsCount, WSLCNetworkManagedLabel);
2818
2679
- auto lock = m_lock.lock_shared();
2680
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient);
2681
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
2819
+ auto lock = AcquireLease();
2820
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2821
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
2822
2823
std::lock_guard networksLock(m_networksLock);
2824
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), m_networks.contains(name));
2865
docker_schema::CreateNetworkResponse createResult;
2866
try
2867
{
2728
- createResult = m_dockerClient->CreateNetwork(request);
2868
+ createResult = m_runtime.Docker().CreateNetwork(request);
2869
}
2870
catch (const DockerHTTPException& e)
2871
{
2879
EMIT_USER_WARNING(wsl::shared::string::MultiByteToWide(createResult.Warning));
2880
}
2881
2742
- auto removeNetworkCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, &name]() { m_dockerClient->RemoveNetwork(name); });
2882
+ auto removeNetworkCleanup =
2883
+ wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this, &name]() { m_runtime.Docker().RemoveNetwork(name); });
2884
2885
// Inspect the newly created network to cache full properties (IPAM, Scope, etc.)
2886
// since CreateNetworkResponse only returns {Id, Warning}.
2887
docker_schema::Network full;
2888
try
2889
{
2749
- full = m_dockerClient->InspectNetwork(name);
2890
+ full = m_runtime.Docker().InspectNetwork(name);
2891
}
2892
catch (const DockerHTTPException& e)
2893
{
2934
std::string name = Name;
2935
ValidateName(name.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH);
2936
2796
- auto lock = m_lock.lock_shared();
2797
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient);
2798
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
2937
+ auto lock = AcquireLease();
2938
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
2939
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
2940
2941
std::lock_guard networksLock(m_networksLock);
2942
2945
2946
try
2947
{
2807
- m_dockerClient->RemoveNetwork(name);
2948
+ m_runtime.Docker().RemoveNetwork(name);
2949
}
2950
catch (const DockerHTTPException& e)
2951
{
2974
*Networks = nullptr;
2975
*Count = 0;
2976
2836
- auto lock = m_lock.lock_shared();
2977
+ auto lock = AcquireLease();
2978
std::lock_guard networksLock(m_networksLock);
2979
2980
if (m_networks.empty())
3013
std::string name = Name;
3014
ValidateName(name.c_str(), WSLC_MAX_NETWORK_NAME_LENGTH);
3015
2875
- auto lock = m_lock.lock_shared();
3016
+ auto lock = AcquireLease();
3017
std::lock_guard networksLock(m_networksLock);
3018
3019
auto it = m_networks.find(name);
3069
// Scope the prune to WSLC-managed networks.
3070
filters["label"].push_back(WSLCNetworkManagedLabel);
3071
2931
- auto lock = m_lock.lock_shared();
2932
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_dockerClient);
2933
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
3072
+ auto lock = AcquireLease();
3073
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
3074
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
3075
3076
std::lock_guard networksLock(m_networksLock);
3077
3078
docker_schema::PruneNetworkResult pruneResult;
3079
try
3080
{
2940
- pruneResult = m_dockerClient->PruneNetworks(filters);
3081
+ pruneResult = m_runtime.Docker().PruneNetworks(filters);
3082
}
3083
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to prune networks");
3084
3149
HRESULT WSLCSession::Terminate()
3150
try
3151
{
3011
- // Ensure only one Terminate() runs. This must be checked before taking m_lock
3012
- // because OnVmExited() is called from the IORelay thread — if an external Terminate()
3013
- // holds m_lock and calls m_ioRelay.Stop(), the relay thread must not re-enter
3014
- // Terminate() and deadlock on m_lock.
3152
+ // Ensure only one Terminate() runs. This must be checked before taking the runtime's exclusive
3153
+ // lock because OnVmExited() is called from the IORelay thread — if an external Terminate()
3154
+ // holds that lock and calls m_runtime.Relay()->Stop(), the relay thread must not re-enter
3155
+ // Terminate() and deadlock on it.
3156
if (m_terminating.exchange(true))
3157
{
3158
return S_OK;
3174
{
3175
std::lock_guard lock(m_userHandlesLock);
3176
3036
- // m_sessionTerminatingEvent is always valid, so it can be signalled without holding m_lock.
3037
- // This allows a session to be unblocked if a stuck operation is holding m_lock.
3177
+ // m_sessionTerminatingEvent is always valid, so it can be signalled without holding the runtime lock.
3178
+ // This allows a session to be unblocked if a stuck operation is holding the runtime lock.
3179
// N.B. This must happen under m_userHandlesLock to synchronize with potentially running operations.
3180
if (!m_sessionTerminatingEvent.is_signaled())
3181
{
3195
CancelUserCOMCallbacks();
3196
}
3197
3057
- sessionLock = m_lock.try_lock_exclusive();
3198
+ sessionLock = m_runtime.TryLockExclusive();
3199
retrying = true;
3200
}
3201
3061
- // Acquire an exclusive lock to ensure that no operation is running.
3062
- WI_VERIFY(sessionLock);
3063
-
3064
- std::lock_guard containersLock(m_containersLock);
3065
- std::lock_guard networksLock(m_networksLock);
3066
-
3067
- m_containers.clear();
3068
- m_volumes.reset();
3069
- m_networks.clear();
3070
-
3071
- // Stop the IO relay.
3072
- // This stops:
3073
- // - container state monitoring.
3074
- // - container init process relays
3075
- // - execs relays
3076
- // - container logs relays
3077
- m_ioRelay.Stop();
3202
+ m_runtime.Shutdown(sessionLock, m_terminationReason, m_terminationDetails);
3203
3204
+ // Idle teardown is disabled and no operation can run past termination, so the parked VM
3205
+ // factory can no longer be re-fetched; revoke it from the GIT.
3206
+ if (m_vmFactoryGitCookie != 0)
3207
{
3080
- std::lock_guard allocatedPortsLock(m_allocatedPortsLock);
3081
- m_allocatedPorts.clear();
3208
+ LOG_IF_FAILED(m_git->RevokeInterfaceFromGlobal(m_vmFactoryGitCookie));
3209
+ m_vmFactoryGitCookie = 0;
3210
}
3211
3084
- m_eventTracker.reset();
3085
- m_dockerClient.reset();
3086
-
3087
- // Check if the VM has already exited (e.g., killed externally).
3088
- // If so, skip operations that require a live VM to avoid unnecessary waits.
3089
- // N.B. m_vmExitedEvent may be uninitialized if Terminate() is called from the
3090
- // Initialize() error path before GetTerminationEvent() succeeds.
3091
- if (m_vmExitedEvent && m_vmExitedEvent.is_signaled())
3092
- {
3093
- WSL_LOG("SkippingGracefulShutdown_VmDead", TraceLoggingValue(m_id, "SessionId"));
3094
-
3095
- // The VM exited on its own, so it recorded the cause.
3096
- if (m_virtualMachine)
3097
- {
3098
- wil::unique_cotaskmem_string details;
3099
- LOG_IF_FAILED(m_virtualMachine->GetTerminationReason(&m_terminationReason, &details));
3100
- m_terminationDetails = details ? details.get() : L"";
3101
- }
3102
- }
3103
- else
3104
- {
3105
- // The VM is still alive, so this is a graceful shutdown initiated by us.
3106
- m_terminationReason = WSLCVirtualMachineTerminationReasonShutdown;
3107
-
3108
- if (m_virtualMachine)
3109
- {
3110
- m_virtualMachine->OnSessionTerminated();
3111
-
3112
- // Stop dockerd first, then containerd (dockerd is a client of containerd).
3113
- // N.B. dockerd waits a couple seconds if there are any outstanding HTTP request sockets opened.
3114
- if (m_dockerdProcess.has_value())
3115
- {
3116
- auto dockerdExitCode = StopProcess(m_dockerdProcess.value(), c_processTerminateTimeoutMs, c_processKillTimeoutMs);
3117
- WSL_LOG("DockerdExit", TraceLoggingValue(dockerdExitCode, "code"));
3118
- }
3119
-
3120
- if (m_containerdProcess.has_value())
3121
- {
3122
- auto containerdExitCode = StopProcess(m_containerdProcess.value(), c_processTerminateTimeoutMs, c_processKillTimeoutMs);
3123
- WSL_LOG("ContainerdExit", TraceLoggingValue(containerdExitCode, "code"));
3124
- }
3125
-
3126
- // N.B. dockerd has exited by this point, so unmounting the VHD is safe since no container can be running.
3127
- if (m_storageMounted)
3128
- {
3129
- try
3130
- {
3131
- m_virtualMachine->Unmount(c_containerdStorage);
3132
- m_storageMounted = false;
3133
- }
3134
- CATCH_LOG();
3135
- }
3136
- }
3137
- }
3138
-
3139
- m_dockerdProcess.reset();
3140
- m_containerdProcess.reset();
3141
- m_virtualMachine.reset();
3142
-
3143
- // Delete the ephemeral swap VHD now that the VM is gone.
3144
- if (!m_swapVhdPath.empty())
3145
- {
3146
- LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(m_swapVhdPath.c_str()));
3147
- m_swapVhdPath.clear();
3148
- }
3149
-
3150
- m_sessionTerminatedEvent.SetEvent();
3151
-
3212
return S_OK;
3213
}
3214
CATCH_RETURN();
3268
}
3269
CATCH_LOG();
3270
3211
-HRESULT WSLCSession::MountWindowsFolder(LPCWSTR WindowsPath, LPCSTR LinuxPath, BOOL ReadOnly)
3271
+HRESULT WSLCSession::MountWindowsFolder(LPCWSTR WindowsPath, LPCSTR LinuxPath, BOOL ReadOnly, BOOL AcquireVmLease)
3272
try
3273
{
3274
WSLCExecutionContext context(this);
3276
RETURN_HR_IF_NULL(E_POINTER, WindowsPath);
3277
RETURN_HR_IF_NULL(E_POINTER, LinuxPath);
3278
3219
- auto lock = m_lock.lock_shared();
3220
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
3279
+ auto lock = AcquireLease(LeasePolicyFor(AcquireVmLease));
3280
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
3281
3222
- return m_virtualMachine->MountWindowsFolder(WindowsPath, LinuxPath, ReadOnly);
3282
+ return m_runtime.Vm().MountWindowsFolder(WindowsPath, LinuxPath, ReadOnly);
3283
}
3284
CATCH_RETURN();
3285
3226
-HRESULT WSLCSession::UnmountWindowsFolder(LPCSTR LinuxPath)
3286
+HRESULT WSLCSession::UnmountWindowsFolder(LPCSTR LinuxPath, BOOL AcquireVmLease)
3287
try
3288
{
3289
WSLCExecutionContext context(this);
3290
3291
RETURN_HR_IF_NULL(E_POINTER, LinuxPath);
3292
3233
- auto lock = m_lock.lock_shared();
3234
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
3293
+ auto lock = AcquireLease(LeasePolicyFor(AcquireVmLease));
3294
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
3295
3236
- return m_virtualMachine->UnmountWindowsFolder(LinuxPath);
3296
+ return m_runtime.Vm().UnmountWindowsFolder(LinuxPath);
3297
}
3298
CATCH_RETURN();
3299
3302
{
3303
WSLCExecutionContext context(this);
3304
3245
- auto lock = m_lock.lock_shared();
3246
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
3305
+ auto lock = AcquireLease();
3306
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
3307
3248
- std::lock_guard allocatedPortsLock(m_allocatedPortsLock);
3308
+ std::lock_guard allocatedPortsLock(m_runtime.AllocatedPortsLock());
3309
3310
// Look for an existing allocation first.
3251
- auto it = m_allocatedPorts.find(LinuxPort);
3311
+ auto& allocatedPorts = m_runtime.AllocatedPorts();
3312
+ auto it = allocatedPorts.find(LinuxPort);
3313
3314
bool inserted = false;
3315
auto cleanup = wil::scope_exit([&]() {
3316
if (inserted)
3317
{
3257
- m_allocatedPorts.erase(it);
3318
+ allocatedPorts.erase(it);
3319
}
3320
});
3321
3261
- if (it == m_allocatedPorts.end())
3322
+ if (it == allocatedPorts.end())
3323
{
3324
// No existing port allocation, create a new one.
3264
- auto allocated = std::make_pair(m_virtualMachine->TryAllocatePort(LinuxPort, Family, IPPROTO_TCP), static_cast<size_t>(0));
3325
+ auto allocated = std::make_pair(m_runtime.Vm().TryAllocatePort(LinuxPort, Family, IPPROTO_TCP), static_cast<size_t>(0));
3326
THROW_HR_IF(HRESULT_FROM_WIN32(WSAEADDRINUSE), allocated.first == nullptr);
3327
3267
- it = m_allocatedPorts.emplace(LinuxPort, allocated).first;
3328
+ it = allocatedPorts.emplace(LinuxPort, allocated).first;
3329
inserted = true;
3330
}
3331
3332
auto mapping = VMPortMapping::LocalhostTcpMapping(Family, WindowsPort);
3333
mapping.AssignVmPort(it->second.first);
3334
3274
- m_virtualMachine->MapPort(mapping);
3335
+ m_runtime.Vm().MapPort(mapping);
3336
3337
// Increase usage count.
3338
it->second.second++;
3349
{
3350
WSLCExecutionContext context(this);
3351
3291
- auto lock = m_lock.lock_shared();
3292
- THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
3352
+ auto lock = AcquireLease();
3353
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasVm());
3354
3294
- std::lock_guard allocatedPortsLock(m_allocatedPortsLock);
3355
+ std::lock_guard allocatedPortsLock(m_runtime.AllocatedPortsLock());
3356
3296
- auto it = m_allocatedPorts.find(LinuxPort);
3297
- RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_allocatedPorts.end());
3357
+ auto& allocatedPorts = m_runtime.AllocatedPorts();
3358
+ auto it = allocatedPorts.find(LinuxPort);
3359
+ RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == allocatedPorts.end());
3360
3361
auto mapping = VMPortMapping::LocalhostTcpMapping(Family, WindowsPort);
3362
mapping.AssignVmPort(it->second.first);
3301
- mapping.Attach(m_virtualMachine.value());
3363
+ mapping.Attach(m_runtime.Vm());
3364
3365
auto cleanup = wil::scope_exit([&]() { mapping.Release(); });
3366
3305
- m_virtualMachine->UnmapPort(mapping);
3367
+ m_runtime.Vm().UnmapPort(mapping);
3368
3369
it->second.second--;
3370
3371
// If usage count drops to 0, release the port allocation.
3372
if (it->second.second == 0)
3373
{
3312
- m_allocatedPorts.erase(it);
3374
+ allocatedPorts.erase(it);
3375
}
3376
3377
return S_OK;
3378
}
3379
CATCH_RETURN();
3380
3381
+HRESULT WSLCSession::TriggerIdleTermination(BOOL* WasAlreadyIdle)
3382
+try
3383
+{
3384
+ WSLCExecutionContext context(this);
3385
+
3386
+ THROW_HR_IF_NULL(E_POINTER, WasAlreadyIdle);
3387
+
3388
+ *WasAlreadyIdle = m_runtime.TriggerIdleTerminationForTest() ? TRUE : FALSE;
3389
+
3390
+ return S_OK;
3391
+}
3392
+CATCH_RETURN();
3393
+
3394
HRESULT WSLCSession::InterfaceSupportsErrorInfo(REFIID riid)
3395
{
3396
return riid == __uuidof(IWSLCSession) || riid == __uuidof(IWSLCCompatSession) ? S_OK : S_FALSE;
3692
3693
void WSLCSession::OnContainerDeleted(const WSLCContainerImpl* Container)
3694
{
3620
- auto lock = m_lock.lock_shared();
3695
+ // N.B. Invoked only from WSLCContainer::Delete, which already holds a VmLease (the shared
3696
+ // session lock). The lease prevents a concurrent idle teardown from clearing m_containers,
3697
+ // so this only needs m_containersLock. It must NOT re-acquire the shared session lock here:
3698
+ // doing so while the idle worker is queued for the exclusive lock would deadlock (recursive
3699
+ // shared acquire behind a pending writer).
3700
std::lock_guard containersLock(m_containersLock);
3701
3702
// N.B. once a container transitions to a 'Deleted' state, a call to ListContainers() can remove it from m_containers.
3747
3748
void WSLCSession::RecoverExistingContainers()
3749
{
3671
- WI_ASSERT(m_dockerClient.has_value());
3672
- WI_ASSERT(m_eventTracker.has_value());
3673
- WI_ASSERT(m_virtualMachine.has_value());
3750
+ WI_ASSERT(m_runtime.HasDocker());
3751
+ WI_ASSERT(m_runtime.HasEvents());
3752
+ WI_ASSERT(m_runtime.HasVm());
3753
3675
- auto containers = m_dockerClient->ListContainers(true); // all=true to include stopped containers
3754
+ auto containers = m_runtime.Docker().ListContainers(true); // all=true to include stopped containers
3755
3756
+ std::lock_guard containersLock(m_containersLock);
3757
for (const auto& dockerContainer : containers)
3758
{
3759
+ // Keep existing wrappers and their client COM references in place, then re-register their
3760
+ // ports against the restarted VM.
3761
+ if (auto existing = m_containers.find(dockerContainer.Id); existing != m_containers.end())
3762
+ {
3763
+ // Isolate recovery failures to this container so one bad container cannot fail lazy start
3764
+ // for every client, mirroring the Open() failure path below.
3765
+ try
3766
+ {
3767
+ existing->second->RecoverPorts(dockerContainer);
3768
+ }
3769
+ catch (...)
3770
+ {
3771
+ LOG_CAUGHT_EXCEPTION_MSG("Failed to recover container state: %hs", dockerContainer.Id.c_str());
3772
+ EMIT_USER_WARNING(
3773
+ Localization::MessageWslcFailedToRecoverContainer(wsl::shared::string::MultiByteToWide(dockerContainer.Id)));
3774
+ }
3775
+ continue;
3776
+ }
3777
+
3778
try
3779
{
3780
auto container = WSLCContainerImpl::Open(
3682
- dockerContainer,
3683
- *this,
3684
- m_virtualMachine.value(),
3685
- m_pluginNotifier.get(),
3686
- m_volumes.value(),
3687
- std::bind(&WSLCSession::OnContainerDeleted, this, std::placeholders::_1),
3688
- m_eventTracker.value(),
3689
- m_dockerClient.value(),
3690
- m_ioRelay);
3781
+ dockerContainer, *this, m_runtime, m_pluginNotifier.get(), std::bind(&WSLCSession::OnContainerDeleted, this, std::placeholders::_1));
3782
3783
auto [it, inserted] = m_containers.emplace(container->ID(), std::move(container));
3784
WI_ASSERT(inserted);
3799
3800
void WSLCSession::RecoverExistingNetworks()
3801
{
3711
- WI_ASSERT(m_dockerClient.has_value());
3712
- WI_ASSERT(m_virtualMachine.has_value());
3802
+ WI_ASSERT(m_runtime.HasDocker());
3803
+ WI_ASSERT(m_runtime.HasVm());
3804
3714
- auto networks = m_dockerClient->ListNetworks();
3805
+ auto networks = m_runtime.Docker().ListNetworks();
3806
3807
std::lock_guard networksLock(m_networksLock);
3808