diff --git a/src/windows/common/COMImplClass.h b/src/windows/common/COMImplClass.h index 53a7a3ae8..fe5a0c7ff 100644 --- a/src/windows/common/COMImplClass.h +++ b/src/windows/common/COMImplClass.h @@ -39,9 +39,23 @@ class COMImplClass }); WI_ASSERT(m_impl != nullptr); + + // Allow subclasses to perform any necessary actions while the impl is still connected, + // such as caching properties that will be needed after disconnect. + OnDisconnectLockHeld(m_impl); + m_impl = nullptr; } +protected: + + // Called from Disconnect() while holding the internal lock, after all in-flight callers have drained + // but before m_impl is nulled. Subclasses can override this to perform any necessary actions while the + // impl is still connected, such as caching properties that will be needed after disconnect. + virtual void OnDisconnectLockHeld(TImpl* /*impl*/) noexcept + { + } + protected: template HRESULT CallImpl(void (TImpl::*routine)(Args... args), Args... args) diff --git a/src/windows/wslasession/WSLAContainer.cpp b/src/windows/wslasession/WSLAContainer.cpp index bb6705f83..04184f5d4 100644 --- a/src/windows/wslasession/WSLAContainer.cpp +++ b/src/windows/wslasession/WSLAContainer.cpp @@ -710,13 +710,12 @@ __requires_exclusive_lock_held(m_lock) void WSLAContainerImpl::DeleteExclusiveLo try { - m_dockerClient.DeleteContainer(m_id, WI_IsFlagSet(Flags, WSLADeleteImageFlagsForce)); + m_dockerClient.DeleteContainer(m_id, WI_IsFlagSet(Flags, WSLADeleteFlagsForce)); } CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to delete container '%hs'", m_id.c_str()); - ReleaseResources(); - Transition(WslaContainerStateDeleted); + ReleaseResources(); } void WSLAContainerImpl::Export(ULONG OutHandle) const @@ -1418,6 +1417,12 @@ HRESULT WSLAContainer::GetState(WSLAContainerState* Result) { COMServiceExecutionContext context; + if (m_cachedState.has_value()) + { + *Result = m_cachedState.value(); + return S_OK; + } + *Result = WslaContainerStateInvalid; return CallImpl(&WSLAContainerImpl::GetState, Result); } @@ -1427,6 +1432,12 @@ HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** Process) COMServiceExecutionContext context; *Process = nullptr; + + if (m_cachedInitProcess) + { + return m_cachedInitProcess.CopyTo(Process); + } + return CallImpl(&WSLAContainerImpl::GetInitProcess, Process); } @@ -1478,6 +1489,23 @@ try } CATCH_RETURN(); +void WSLAContainer::OnDisconnectLockHeld(WSLAContainerImpl* impl) noexcept +try +{ + m_cachedId = impl->m_id; + m_cachedName = impl->m_name; + m_cachedState = impl->m_state; + + { + std::lock_guard processesLock{impl->m_processesLock}; + if (impl->m_initProcess) + { + impl->m_initProcess.CopyTo(m_cachedInitProcess.GetAddressOf()); + } + } +} +CATCH_LOG(); + HRESULT WSLAContainer::Export(ULONG OutHandle) { COMServiceExecutionContext context; @@ -1501,6 +1529,12 @@ try { COMServiceExecutionContext context; + if (m_cachedId.has_value()) + { + WI_VERIFY(strcpy_s(Id, std::size(WSLAContainerId{}), m_cachedId->c_str()) == 0); + return S_OK; + } + auto [lock, impl] = LockImpl(); WI_VERIFY(strcpy_s(Id, std::size(WSLAContainerId{}), impl->ID().c_str()) == 0); @@ -1515,6 +1549,12 @@ try *Name = nullptr; + if (m_cachedName.has_value()) + { + *Name = wil::make_unique_ansistring(m_cachedName->c_str()).release(); + return S_OK; + } + auto [lock, impl] = LockImpl(); *Name = wil::make_unique_ansistring(impl->Name().c_str()).release(); diff --git a/src/windows/wslasession/WSLAContainer.h b/src/windows/wslasession/WSLAContainer.h index ee80b2fd9..912450db2 100644 --- a/src/windows/wslasession/WSLAContainer.h +++ b/src/windows/wslasession/WSLAContainer.h @@ -106,6 +106,10 @@ class WSLAContainerImpl DockerHTTPClient& DockerClient, IORelay& Relay); + // WSLAContainer needs direct access to impl fields in OnDisconnectLockHeld() to cache + // read-only properties without re-acquiring m_lock (which would deadlock). + friend class WSLAContainer; + private: __requires_exclusive_lock_held(m_lock) void DeleteExclusiveLockHeld(WSLADeleteFlags Flags); @@ -114,6 +118,7 @@ class WSLAContainerImpl __requires_exclusive_lock_held(m_lock) void ReleaseResources(); __requires_exclusive_lock_held(m_lock) void ReleaseRuntimeResources(); __requires_exclusive_lock_held(m_lock) void DisconnectComWrapper(); + std::unique_ptr CreateRelayedProcessIO(wil::unique_handle&& stream, WSLAProcessFlags flags); wsl::windows::common::wsla_schema::InspectContainer BuildInspectContainer(const wsl::windows::common::docker_schema::InspectContainer& dockerInspect) const; @@ -170,7 +175,18 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer IFACEMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); +protected: + void OnDisconnectLockHeld(WSLAContainerImpl* impl) noexcept override; + private: std::function m_onDeleted; + + // Cached read-only properties populated by OnDisconnectLockHeld() so they remain + // accessible after the impl is disconnected. + std::optional m_cachedId; + std::optional m_cachedName; + std::optional m_cachedState; + Microsoft::WRL::ComPtr m_cachedInitProcess; }; -} // namespace wsl::windows::service::wsla \ No newline at end of file + +} // namespace wsl::windows::service::wsla diff --git a/test/windows/WSLATests.cpp b/test/windows/WSLATests.cpp index 074c254d0..c025cb49a 100644 --- a/test/windows/WSLATests.cpp +++ b/test/windows/WSLATests.cpp @@ -3262,9 +3262,9 @@ class WSLATests VERIFY_ARE_EQUAL(container->State(), WslaContainerStateExited); VERIFY_SUCCEEDED(container->Get().Delete(WSLADeleteFlagsNone)); + VERIFY_ARE_EQUAL(container->State(), WslaContainerStateDeleted); - WSLAContainerState state{}; - VERIFY_ARE_EQUAL(container->Get().GetState(&state), RPC_E_DISCONNECTED); + VERIFY_ARE_EQUAL(container->Get().Delete(WSLADeleteFlagsNone), RPC_E_DISCONNECTED); } // Validate that containers behave correctly if they outlive their session. @@ -5177,6 +5177,38 @@ class WSLATests } } + TEST_METHOD(ContainerAutoRemoveReadStdout) + { + WSLAContainerLauncher launcher("debian:latest", "test-auto-remove-stdout", {"echo", "Hello World"}); + launcher.SetContainerFlags(WSLAContainerFlagsRm); + + auto container = launcher.Launch(*m_defaultSession); + + // Wait for the container to exit and verify it gets deleted automatically. + wsl::shared::retry::RetryWithTimeout( + [&]() { THROW_WIN32_IF(ERROR_RETRY, container.State() != WslaContainerStateDeleted); }, + std::chrono::milliseconds{100}, + std::chrono::seconds{30}); + + VERIFY_ARE_EQUAL(WslaContainerStateDeleted, container.State()); + VERIFY_ARE_EQUAL(container.Get().Delete(WSLADeleteFlagsNone), RPC_E_DISCONNECTED); + + // Ensure we can still get the init process and read stdout. + auto process = container.GetInitProcess(); + auto result = process.WaitAndCaptureOutput(); + + VERIFY_ARE_EQUAL(0, result.Code); + VERIFY_ARE_EQUAL(std::string("Hello World\n"), result.Output[1]); + + // Validate that the container is not found if we try to open it by name or id, or found in the container list. + wil::com_ptr notFound; + VERIFY_ARE_EQUAL(m_defaultSession->OpenContainer("test-auto-remove-stdout", ¬Found), HRESULT_FROM_WIN32(ERROR_NOT_FOUND)); + + wil::unique_cotaskmem_array_ptr containers; + VERIFY_SUCCEEDED(m_defaultSession->ListContainers(&containers, containers.size_address())); + VERIFY_ARE_EQUAL(containers.size(), 0); + } + TEST_METHOD(ContainerNameGeneration) { WSL2_TEST_ONLY();