Implement container state management

This commit is contained in:
Blue 2025-12-03 16:07:06 -08:00
parent c826c20c8d
commit 49462adfb9
10 changed files with 108 additions and 130 deletions

View File

@ -95,6 +95,15 @@ std::pair<int, bool> RunningWSLAProcess::GetExitState()
return {code, state == WslaProcessStateSignalled};
}
WSLA_PROCESS_STATE RunningWSLAProcess::State()
{
WSLA_PROCESS_STATE state{};
int code{};
GetState(&state, &code);
return state;
}
std::string WSLAProcessLauncher::FormatResult(const RunningWSLAProcess::ProcessResult& result)
{
auto stdOut = result.Output.find(1);

View File

@ -50,6 +50,7 @@ public:
virtual wil::unique_handle GetStdHandle(int Index) = 0;
virtual wil::unique_event GetExitEvent() = 0;
std::pair<int, bool> GetExitState();
WSLA_PROCESS_STATE State();
protected:
virtual void GetState(WSLA_PROCESS_STATE* State, int* Code) = 0;

View File

@ -13,7 +13,6 @@ set(SOURCES
set(HEADERS
ServiceProcessLauncher.h
WeakRefContainer.h
WSLAContainer.h
WSLAProcess.h
WSLASession.h

View File

@ -16,36 +16,29 @@ Abstract:
#include "WSLAContainer.h"
#include "WSLAProcess.h"
using wsl::windows::service::wsla::WeakReference;
using wsl::windows::service::wsla::WSLAContainer;
constexpr const char* nerdctlPath = "/usr/bin/nerdctl";
// Constants for required default arguments for "nerdctl run..."
static std::vector<std::string> defaultNerdctlRunArgs{
//"--pull=never", // TODO: Uncomment once PullImage() is implemented.
"--net=host", // TODO: default for now, change later
"--ulimit",
"nofile=65536:65536"};
static std::vector<std::string> defaultNerdctlRunArgs{//"--pull=never", // TODO: Uncomment once PullImage() is implemented.
"--net=host", // TODO: default for now, change later
"--ulimit",
"nofile=65536:65536"};
WSLAContainer::WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess, const char* name, const char* image) :
WeakReference<WSLAContainer>(), m_parentVM(parentVM), m_containerProcess(std::move(containerProcess)), m_name(name), m_image(image)
m_parentVM(parentVM), m_containerProcess(std::move(containerProcess)), m_name(name), m_image(image)
{
}
WSLAContainer::~WSLAContainer()
const std::string& WSLAContainer::Name() const noexcept
{
OnDestroy();
return m_name;
}
void WSLAContainer::GetName(char Name[WSLA_MAX_CONTAINER_NAME_LENGTH + 1]) const noexcept
const std::string& WSLAContainer::Image() const noexcept
{
WI_VERIFY(strcpy_s(Name, sizeof(Name), m_name.c_str()) == 0);
}
void WSLAContainer::GetImage(char Image[WSLA_MAX_IMAGE_NAME_LENGTH + 1]) const noexcept
{
WI_VERIFY(strcpy_s(Image, WSLA_MAX_IMAGE_NAME_LENGTH + 1, m_image.c_str()) == 0);
return m_image;
}
HRESULT WSLAContainer::Start()
@ -65,7 +58,17 @@ HRESULT WSLAContainer::Delete()
HRESULT WSLAContainer::GetState(WSLA_CONTAINER_STATE* State)
{
return E_NOTIMPL;
if (m_containerProcess.State() == WSLAProcessStateRunning)
{
*State = WslaContainerStateRunning;
}
else
{
// TODO: handle failure to start.
*State = WslaContainerStateExited;
}
return S_OK;
}
HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** Process)
@ -110,7 +113,6 @@ Microsoft::WRL::ComPtr<WSLAContainer> WSLAContainer::Create(const WSLA_CONTAINER
if (hasStdin)
{
// For now return a proper error if the caller tries to pass stdin without a TTY to prevent hangs.
THROW_WIN32_IF(ERROR_NOT_SUPPORTED, hasTty == false);
inputOptions.push_back("-i");
}
@ -127,7 +129,7 @@ Microsoft::WRL::ComPtr<WSLAContainer> WSLAContainer::Create(const WSLA_CONTAINER
launcher.AddFd(containerOptions.InitProcessOptions.Fds[i]);
}
return wil::MakeOrThrow<WSLAContainer>(&parentVM, launcher.Launch(parentVM));
return wil::MakeOrThrow<WSLAContainer>(&parentVM, launcher.Launch(parentVM), containerOptions.Name, containerOptions.Image);
}
std::vector<std::string> WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector<std::string>&& inputOptions)

View File

@ -24,11 +24,10 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLAContainer, IFastRundown>
{
public:
WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess, const char* name, const char* image);
~WSLAContainer();
NON_COPYABLE(WSLAContainer);
WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess, const char* name, const char* image);
IFACEMETHOD(Start)() override;
IFACEMETHOD(Stop)(_In_ int Signal, _In_ ULONG TimeoutMs) override;
IFACEMETHOD(Delete)() override;
@ -36,8 +35,8 @@ public:
IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override;
IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override;
void GetName(char Name[WSLA_MAX_IMAGE_NAME_LENGTH + 1]) const noexcept;
void GetImage(char Name[WSLA_MAX_CONTAINER_NAME_LENGTH + 1]) const noexcept;
const std::string& Name() const noexcept;
const std::string& Image() const noexcept;
static Microsoft::WRL::ComPtr<WSLAContainer> Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM);

View File

@ -226,10 +226,10 @@ try
// TODO: Log entrance into the function.
auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get());
m_containers.Add(container.Get());
THROW_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container));
m_containers.emplace_back(std::move(container));
return S_OK;
}
CATCH_RETURN();
@ -239,19 +239,30 @@ HRESULT WSLASession::OpenContainer(LPCWSTR Name, IWSLAContainer** Container)
return E_NOTIMPL;
}
HRESULT WSLASession::ListContainers(WSLA_CONTAINER** Images, ULONG* Count)
HRESULT WSLASession::ListContainers(WSLA_CONTAINER** Containers, ULONG* Count)
try
{
auto lockedElements = m_containers.Get();
*Count = 0;
*Containers = nullptr;
std::lock_guard lock{m_lock};
auto output = wil::make_unique_cotaskmem<WSLA_CONTAINER[]>(m_containers.size());
auto output = wil::make_unique_cotaskmem<WSLA_CONTAINER[]>(lockedElements.elements.size());
size_t index = 0;
for (const auto &e: lockedElements.elements)
for (const auto& e : m_containers)
{
e->GetImage(output[index].Image);
e->GetName(output[index].Name);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Image, e->Image().c_str()) != 0);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Name, e->Name().c_str()) != 0);
THROW_IF_FAILED(e->GetState(&output[index].State));
index++;
}
*Count = static_cast<ULONG>(m_containers.size());
*Containers = output.release();
return S_OK;
}
CATCH_RETURN();
HRESULT WSLASession::GetVirtualMachine(IWSLAVirtualMachine** VirtualMachine)
{

View File

@ -16,7 +16,6 @@ Abstract:
#include "wslaservice.h"
#include "WSLAVirtualMachine.h"
#include "WeakRefContainer.h"
#include "WSLAContainer.h"
namespace wsl::windows::service::wsla {
@ -64,7 +63,7 @@ private:
Microsoft::WRL::ComPtr<WSLAVirtualMachine> m_virtualMachine;
std::wstring m_displayName;
std::filesystem::path m_storageVhdPath;
WeakRefContainer<WSLAContainer> m_containers;
std::vector<Microsoft::WRL::ComPtr<WSLAContainer>> m_containers;
std::mutex m_lock;
// TODO: Add container tracking here. Could reuse m_lock for that.

View File

@ -1,93 +0,0 @@
#pragma once
#include "defs.h"
#include <unordered_set>
#include <wil/com.h>
#include <wil/cppwinrt.h>
namespace wsl::windows::service::wsla {
template <typename T>
class WeakRefContainer
{
public:
struct LockedElements
{
std::lock_guard<std::mutex> lock;
std::unordered_set<T*>& elements;
};
WeakRefContainer(const WeakRefContainer&) = delete;
WeakRefContainer(WeakRefContainer&&) = delete;
WeakRefContainer& operator=(const WeakRefContainer&);
WeakRefContainer& operator=(WeakRefContainer&&);
WeakRefContainer() = default;
~WeakRefContainer()
{
std::lock_guard<std::mutex> guard(m_lock);
for (const auto& e : m_elements)
{
e->SetContainer(nullptr);
}
}
void Add(T* element)
{
std::lock_guard<std::mutex> guard(m_lock);
element->SetContainer(this);
m_elements.insert(element);
}
void Remove(T* element)
{
element->SetContainer(nullptr);
std::lock_guard<std::mutex> guard(m_lock);
m_elements.erase(element);
}
LockedElements Get()
{
return {std::lock_guard<std::mutex>(m_lock), m_elements};
}
private:
std::unordered_set<T*> m_elements;
std::mutex m_lock;
};
template <typename T>
class WeakReference
{
public:
NON_COPYABLE(WeakReference);
WeakReference() = default;
void SetContainer(WeakRefContainer<T>* container) noexcept
{
std::lock_guard<std::mutex> guard(m_lock);
m_container = container;
}
protected:
WeakRefContainer<T>* m_container = nullptr;
void OnDestroy()
{
std::lock_guard<std::mutex> guard(m_lock);
if (m_container != nullptr)
{
m_container->Remove(static_cast<T*>(this));
m_container = nullptr;
}
}
private:
std::mutex m_lock;
};
} // namespace wsl::windows::service::wsla

View File

@ -162,7 +162,6 @@ enum WSLA_CONTAINER_STATE
WslaContainerStateCreated = 1,
WslaContainerStateRunning = 2,
WslaContainerStateExited = 3,
WslaContainerStateFailed = 4,
// TODO: More states might be added to reflect all nerdctl's states.
};

View File

@ -1129,4 +1129,56 @@ class WSLATests
#endif
}
};
TEST_METHOD(ContainerState)
{
WSL2_TEST_ONLY();
SKIP_TEST_ARM64();
auto settings = GetDefaultSessionSettings();
settings.NetworkingMode = WSLANetworkingModeNAT;
auto session = CreateSession(settings);
auto expectContainerList = [&](const std::vector<std::tuple<std::string, std::string, WSLA_CONTAINER_STATE>>& expectedContainers) {
wil::unique_cotaskmem_array_ptr<WSLA_CONTAINER> containers;
VERIFY_SUCCEEDED(session->ListContainers(&containers, containers.size_address<ULONG>()));
VERIFY_ARE_EQUAL(expectedContainers.size(), containers.size());
for (size_t i = 0; i < expectedContainers.size(); i++)
{
const auto& [expectedName, expectedImage, expectedState] = expectedContainers[i];
VERIFY_ARE_EQUAL(expectedName, containers[i].Name);
VERIFY_ARE_EQUAL(expectedImage, containers[i].Image);
VERIFY_ARE_EQUAL(expectedState, containers[i].State);
}
};
// Create a stuck container.
WSLAContainerLauncher launcher(
"debian:latest", "test-container-1", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr);
auto container = launcher.Launch(*session);
// Verify that the container is in running state.
VERIFY_ARE_EQUAL(container.State(), WslaContainerStateRunning);
expectContainerList({{"test-container-1", "debian:latest", WslaContainerStateRunning}});
// Kill the container init process and expect it to be in exited state.
auto initProcess = container.GetInitProcess();
initProcess.Get().Signal(9);
// Wait for the process to actually exit.
wsl::shared::retry::RetryWithTimeout<void>(
[&]() {
initProcess.GetExitState(); // Throw if the process hasn't exited yet.
},
std::chrono::milliseconds{100},
std::chrono::seconds{30});
// Expect the container to be in exited state.
VERIFY_ARE_EQUAL(container.State(), WslaContainerStateExited);
expectContainerList({{"test-container-1", "debian:latest", WslaContainerStateExited}});
}
};