Skip to content
14 changes: 14 additions & 0 deletions src/windows/common/COMImplClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename... Args>
HRESULT CallImpl(void (TImpl::*routine)(Args... args), Args... args)
Expand Down
46 changes: 43 additions & 3 deletions src/windows/wslasession/WSLAContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand All @@ -1501,6 +1529,12 @@ try
{
COMServiceExecutionContext context;

if (m_cachedId.has_value())
{
WI_VERIFY(strcpy_s(Id, std::size<char>(WSLAContainerId{}), m_cachedId->c_str()) == 0);
return S_OK;
}

auto [lock, impl] = LockImpl();
WI_VERIFY(strcpy_s(Id, std::size<char>(WSLAContainerId{}), impl->ID().c_str()) == 0);

Expand All @@ -1515,6 +1549,12 @@ try

*Name = nullptr;

if (m_cachedName.has_value())
{
*Name = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(m_cachedName->c_str()).release();
return S_OK;
}

auto [lock, impl] = LockImpl();

*Name = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(impl->Name().c_str()).release();
Expand Down
18 changes: 17 additions & 1 deletion src/windows/wslasession/WSLAContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +109 to +111

private:
__requires_exclusive_lock_held(m_lock) void DeleteExclusiveLockHeld(WSLADeleteFlags Flags);

Expand All @@ -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<RelayedProcessIO> CreateRelayedProcessIO(wil::unique_handle&& stream, WSLAProcessFlags flags);

wsl::windows::common::wsla_schema::InspectContainer BuildInspectContainer(const wsl::windows::common::docker_schema::InspectContainer& dockerInspect) const;
Expand Down Expand Up @@ -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<void(const WSLAContainerImpl*)> m_onDeleted;

// Cached read-only properties populated by OnDisconnectLockHeld() so they remain
// accessible after the impl is disconnected.
std::optional<std::string> m_cachedId;
std::optional<std::string> m_cachedName;
std::optional<WSLAContainerState> m_cachedState;
Microsoft::WRL::ComPtr<IWSLAProcess> m_cachedInitProcess;
};
} // namespace wsl::windows::service::wsla

} // namespace wsl::windows::service::wsla
36 changes: 34 additions & 2 deletions test/windows/WSLATests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<void>(
[&]() { 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<IWSLAContainer> notFound;
VERIFY_ARE_EQUAL(m_defaultSession->OpenContainer("test-auto-remove-stdout", &notFound), HRESULT_FROM_WIN32(ERROR_NOT_FOUND));

wil::unique_cotaskmem_array_ptr<WSLAContainerEntry> containers;
VERIFY_SUCCEEDED(m_defaultSession->ListContainers(&containers, containers.size_address<ULONG>()));
VERIFY_ARE_EQUAL(containers.size(), 0);
}

TEST_METHOD(ContainerNameGeneration)
{
WSL2_TEST_ONLY();
Expand Down
Loading