Add WSLCVolumes class to track volumes with Docker event-driven synchronization#40300
Add WSLCVolumes class to track volumes with Docker event-driven synchronization#40300kvega005 wants to merge 17 commits intomicrosoft:feature/wsl-for-appsfrom
Conversation
…SLCVolumes manager
Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors WSLC volume management into a new WSLCVolumes component and broadens Docker event handling by replacing ContainerEventTracker with DockerEventTracker, enabling event-driven synchronization for both containers and volumes.
Changes:
- Introduces
WSLCVolumesto own volume recovery, tracking, and COM-facing volume operations. - Replaces
ContainerEventTrackerwithDockerEventTracker, adding volume lifecycle event handling and “wait for create/destroy event” helpers. - Updates container volume behavior/tests to allow Docker to auto-create missing named volumes and to validate anonymous volume cleanup via the new tracking.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp | Updates E2E expectation: named volume is auto-created and removable. |
| test/windows/WSLCTests.cpp | Switches anonymous volume listing/cleanup logic to use ListVolumes() and event-driven waits. |
| src/windows/wslcsession/WSLCVolumes.h | Adds new volume-tracking class interface. |
| src/windows/wslcsession/WSLCVolumes.cpp | Implements volume recovery, event callbacks, and volume CRUD/inspect/list. |
| src/windows/wslcsession/WSLCVhdVolume.h | Marks OnDeleted() as override to match new base hook. |
| src/windows/wslcsession/WSLCSession.h | Replaces raw volume maps/locks with std::optional<WSLCVolumes> and swaps in DockerEventTracker. |
| src/windows/wslcsession/WSLCSession.cpp | Wires up WSLCVolumes + DockerEventTracker; routes volume COM methods through WSLCVolumes. |
| src/windows/wslcsession/WSLCProcessControl.h / .cpp | Updates process controls to use DockerEventTracker references. |
| src/windows/wslcsession/WSLCGuestVolume.cpp | Removes WSLC metadata label dependency for guest volumes; opens from Docker fields directly. |
| src/windows/wslcsession/WSLCContainer.h / .cpp | Removes session-side named-volume existence checks; adds waits for Docker create/destroy events. |
| src/windows/wslcsession/IWSLCVolume.h | Adds OnDeleted() hook for “Docker already destroyed the volume” cleanup. |
| src/windows/wslcsession/DockerHTTPClient.h / .cpp | Adds InspectVolume() endpoint wrapper. |
| src/windows/wslcsession/DockerEventTracker.h / .cpp | New tracker for container+volume events and object create/destroy waiting. |
| src/windows/wslcsession/ContainerEventTracker.h / .cpp | Removes the old container-only event tracker. |
| src/windows/wslcsession/CMakeLists.txt | Updates build inputs for new/removed tracker and new WSLCVolumes. |
| src/windows/inc/docker_schema.h | Extends InspectContainer schema to include Mounts. |
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…into WslcVolumesManager
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| // Validate that all named volumes mounted by the container were successfully recovered | ||
| // by the volumes manager. If any are missing (e.g. backing VHD removed while the service was | ||
| // down), refuse to open the container so it cannot enter a broken state. | ||
| for (const auto& mount : dockerContainer.Mounts) | ||
| { | ||
| if (mount.Type == "volume" && !mount.Name.empty()) | ||
| { | ||
| THROW_HR_IF_MSG( | ||
| E_INVALIDARG, | ||
| !volumes.ContainsVolume(mount.Name), | ||
| "Cannot open container %hs: referenced volume '%hs' is not available", | ||
| dockerContainer.Id.c_str(), | ||
| mount.Name.c_str()); | ||
| } |
There was a problem hiding this comment.
Container recovery now hard-fails if any mounted volume name isn’t present in WSLCVolumes. Given WSLCVolumes currently only tracks volumes it can successfully Open(), this can break recovery for containers that reference valid Docker volumes that WSLC chooses not to track (or failed to open due to stricter validation). Either ensure WSLCVolumes reliably tracks all volume mounts a container might reference (including non-WSLC local volumes), or scope this check to WSLC-managed volumes only (e.g., only enforce for volumes with the WSLC metadata label / VHD driver), and return a user-facing error (localized) when the check fails so callers don’t just get E_INVALIDARG.
| if (vol.Labels.has_value() && vol.Labels->contains(WSLCVolumeMetadataLabel)) | ||
| { | ||
| auto metadata = wsl::shared::FromJson<WSLCVolumeMetadata>(vol.Labels->at(WSLCVolumeMetadataLabel).c_str()); | ||
|
|
||
| if (metadata.Driver == WSLCVhdVolumeDriver) | ||
| { | ||
| m_volumes.insert({vol.Name, WSLCVhdVolumeImpl::Open(vol, m_virtualMachine, m_dockerClient)}); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| m_volumes.insert({vol.Name, WSLCGuestVolumeImpl::Open(vol, m_dockerClient)}); | ||
| } |
There was a problem hiding this comment.
WSLCVolumes::OpenVolumeExclusiveLockHeld treats any volume with a WSLC metadata label but an unrecognized metadata.Driver as a guest volume (falls through to WSLCGuestVolumeImpl::Open). This can silently mis-handle future/unknown WSLC drivers and potentially leak host-side resources (e.g., if a new driver needs cleanup). Consider explicitly handling only the known drivers (vhd/guest) and skipping/logging unknown metadata.Driver values instead of defaulting to guest.
| // Clean up any leaked anonymous volumes when this block exits. | ||
| auto volumeCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { | ||
| auto volumes = listAnonymousVolumes(); | ||
| for (const auto& name : volumes) | ||
| { | ||
| LOG_IF_FAILED(m_defaultSession->DeleteVolume(name.c_str())); | ||
| } |
There was a problem hiding this comment.
This cleanup scope deletes every volume returned by listAnonymousVolumes(), which currently includes all volumes with Driver == "guest". If the session contains other guest volumes (named volumes created by other tests), this cleanup will remove them and can cascade into unrelated failures. Tighten the selection criteria (e.g., track the specific volume names created during this test, or filter on Docker’s anonymous label).
| std::unique_ptr<WSLCGuestVolumeImpl> WSLCGuestVolumeImpl::Open(const wsl::windows::common::docker_schema::Volume& Volume, DockerHTTPClient& DockerClient) | ||
| { | ||
| THROW_HR_IF(E_INVALIDARG, !Volume.Labels.has_value()); | ||
|
|
||
| auto metadataIt = Volume.Labels->find(WSLCVolumeMetadataLabel); | ||
| THROW_HR_IF(E_INVALIDARG, metadataIt == Volume.Labels->end()); | ||
|
|
||
| auto metadata = wsl::shared::FromJson<WSLCVolumeMetadata>(metadataIt->second.c_str()); | ||
| THROW_HR_IF(E_INVALIDARG, metadata.Driver != WSLCGuestVolumeDriver); | ||
|
|
||
| THROW_HR_IF(E_INVALIDARG, Volume.Driver != "local"); | ||
|
|
||
| if (Volume.Options.has_value()) | ||
| { | ||
| ValidateDriverOpts(Volume.Options.value()); | ||
| } | ||
|
|
||
| // Extract user labels (all labels except our internal metadata label). | ||
| std::map<std::string, std::string> userLabels; | ||
| for (const auto& [key, value] : *Volume.Labels) | ||
| { | ||
| if (key != WSLCVolumeMetadataLabel) | ||
| { | ||
| userLabels[key] = value; | ||
| } | ||
| } | ||
|
|
||
| auto volume = std::make_unique<WSLCGuestVolumeImpl>( | ||
| std::string{Volume.Name}, std::string{Volume.CreatedAt}, std::move(metadata.DriverOpts), std::move(userLabels), DockerClient); | ||
| std::map<std::string, std::string> driverOpts = Volume.Options.value_or(std::map<std::string, std::string>{}); | ||
| std::map<std::string, std::string> labels = Volume.Labels.value_or(std::map<std::string, std::string>{}); | ||
|
|
||
| return volume; | ||
| return std::make_unique<WSLCGuestVolumeImpl>( | ||
| std::string{Volume.Name}, std::string{Volume.CreatedAt}, std::move(driverOpts), std::move(labels), DockerClient); | ||
| } |
There was a problem hiding this comment.
WSLCGuestVolumeImpl::Open now accepts any Docker "local" volume (no WSLC metadata required) and still enforces ValidateDriverOpts (only empty or type=tmpfs). This means non-WSLC local volumes created with common options (e.g. type=none,o=bind,device=...) will fail to open, causing WSLCVolumes recovery to skip them and later container recovery to fail when checking ContainsVolume(). Consider distinguishing WSLC-managed guest volumes from arbitrary Docker volumes (e.g. keep a WSLC metadata/label for guest volumes, or relax validation for volumes without WSLC labels, or track unknown volumes with a minimal implementation that doesn’t enforce WSLC-specific constraints).
Summary of the Pull Request
Refactors WSLC volume management into a dedicated WSLCVolumes class and replaces the old ContainerEventTracker with a broader DockerEventTracker that tracks both container and volume lifecycle events.
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed