Save state

This commit is contained in:
Blue 2025-12-05 18:16:45 -08:00
parent cdbe1d2204
commit 22bc01dcb3
8 changed files with 64 additions and 101 deletions

View File

@ -1011,8 +1011,7 @@ IOHandleStatus OverlappedIOHandle::GetState() const
return State;
}
EventHandle::EventHandle(HANDLE Handle, std::function<void()>&& OnSignalled) :
Handle(Handle), OnSignalled(std::move(OnSignalled))
EventHandle::EventHandle(HANDLE Handle, std::function<void()>&& OnSignalled) : Handle(Handle), OnSignalled(std::move(OnSignalled))
{
}

View File

@ -30,7 +30,7 @@ void ContainerEventTracker::ContainerTrackingReference::Reset()
{
if (m_tracker != nullptr)
{
m_tracker->UnregisterContainerStateUpdates(m_tracker != nullptr);
m_tracker->UnregisterContainerStateUpdates(m_id);
m_tracker = nullptr;
}
}
@ -92,22 +92,17 @@ void ContainerEventTracker::OnEvent(const std::string& event)
auto innerEventJson = details->get<std::string>();
auto innerEvent = nlohmann::json::parse(innerEventJson);
auto containerId = innerEvent.find("container_id");
THROW_HR_IF_MSG(E_INVALIDARG, containerId == innerEvent.end(), "Failed to parse json: %hs", innerEventJson.c_str());
WSL_LOG(
"ContainerStateChange",
TraceLoggingValue(containerId->get<std::string>().c_str(), "Id"),
TraceLoggingValue((int)it->second, "State"));
auto containerIdIt = innerEvent.find("container_id");
THROW_HR_IF_MSG(E_INVALIDARG, containerIdIt == innerEvent.end(), "Failed to parse json: %hs", innerEventJson.c_str());
std::lock_guard lock{m_lock};
auto containerEntry = m_callbacks.find(containerId->get<std::string>());
if (containerEntry != m_callbacks.end())
std::string containerId = containerIdIt->get<std::string>();
for (const auto& e : m_callbacks)
{
for (auto& [id, callback] : containerEntry->second)
if (e.ContainerId == containerId)
{
callback(it->second);
e.Callback(it->second);
}
}
}
@ -157,9 +152,10 @@ ContainerEventTracker::ContainerTrackingReference ContainerEventTracker::Registe
const std::string& ContainerId, ContainerStateChangeCallback&& Callback)
{
std::lock_guard lock{m_lock};
auto id = callbackId++;
m_callbacks[ContainerId][id] = std::move(Callback);
auto id = m_callbackId++;
m_callbacks.emplace_back(id, ContainerId, std::move(Callback));
return ContainerTrackingReference{this, id};
}
@ -167,19 +163,8 @@ void ContainerEventTracker::UnregisterContainerStateUpdates(size_t Id)
{
std::lock_guard lock{m_lock};
for (auto& [containerId, callbacks] : m_callbacks)
{
auto it = callbacks.find(Id);
if (it != callbacks.end())
{
callbacks.erase(it);
if (callbacks.empty())
{
m_callbacks.erase(containerId);
}
return;
}
}
auto remove = std::ranges::remove_if(m_callbacks, [Id](auto& entry) { return entry.CallbackId == Id; });
WI_ASSERT(remove.size() == 1);
WI_ASSERT(false);
m_callbacks.erase(remove.begin(), remove.end());
}

View File

@ -50,10 +50,18 @@ public:
private:
void Run(ServiceRunningProcess& process);
std::map<std::string, std::map<size_t, ContainerStateChangeCallback>> m_callbacks;
struct Callback
{
size_t CallbackId;
std::string ContainerId;
ContainerStateChangeCallback Callback;
};
std::vector<Callback> m_callbacks;
std::thread m_thread;
wil::unique_event m_stopEvent{wil::EventOptions::ManualReset};
std::mutex m_lock;
std::atomic<size_t> callbackId;
std::atomic<size_t> m_callbackId{0};
};
} // namespace wsl::windows::service::wsla

View File

@ -31,8 +31,7 @@ WSLAContainer::WSLAContainer(WSLAVirtualMachine* parentVM, const WSLA_CONTAINER_
{
m_state = WslaContainerStateCreated;
m_trackingReference =
tracker.RegisterContainerStateUpdates(m_id, std::bind(&WSLAContainer::OnStateChange, this, std::placeholders::_1));
m_trackingReference = tracker.RegisterContainerStateUpdates(m_id, std::bind(&WSLAContainer::OnEvent, this, std::placeholders::_1));
}
WSLAContainer::~WSLAContainer()
@ -64,70 +63,30 @@ void WSLAContainer::Start(const WSLA_CONTAINER_OPTIONS& Options)
m_containerProcess = launcher.Launch(*m_parentVM);
auto newState = WaitForTransition(WslaContainerStateRunning);
THROW_HR_IF_MSG(E_FAIL, newState != WslaContainerStateRunning, "Failed to start container '%hs', state: %i", m_name.c_str(), newState);
// Wait for either the container to get to into a 'started' state, or the nerdctl process to exit.
common::relay::MultiHandleWait wait;
wait.AddHandle(std::make_unique<common::relay::EventHandle>(m_containerProcess->GetExitEvent(), [&]() { wait.Cancel(); }));
wait.AddHandle(std::make_unique<common::relay::EventHandle>(m_startedEvent.get(), [&]() { wait.Cancel(); }));
wait.Run({});
// TODO: Actually check the nerdctl status there
THROW_HR_IF_MSG(E_FAIL, !m_startedEvent.is_signaled(), "Failed to start container '%hs'", m_name.c_str());
m_state = WslaContainerStateRunning;
}
WSLA_CONTAINER_STATE WSLAContainer::WaitForTransition(WSLA_CONTAINER_STATE expectedState)
void WSLAContainer::OnEvent(ContainerEvent event)
{
wil::shared_event transitionEvent;
if (event == ContainerEvent::Start)
{
std::lock_guard lock{m_stateLock};
if (m_state == expectedState)
{
return m_state; // Already in expected state, return immediately
}
transitionEvent = m_stateChangeEvent;
}
m_stateChangeEvent.wait();
std::lock_guard lock{m_stateLock};
return m_state;
}
void WSLAContainer::OnStateChange(ContainerEvent event)
{
std::lock_guard lock{m_stateLock};
WSLA_CONTAINER_STATE newState{};
switch (event)
{
case ContainerEvent::Start:
newState = WslaContainerStateRunning;
break;
case ContainerEvent::Stop:
case ContainerEvent::Exit:
newState = WslaContainerStateExited;
break;
case ContainerEvent::Destroy:
newState = WslaContainerStateDeleted;
break;
default:
WI_ASSERT(false);
}
if (newState == m_state)
{
return;
m_startedEvent.SetEvent();
}
WSL_LOG(
"ContainerStateChange",
"ContainerEvent",
TraceLoggingValue(m_name.c_str(), "Name"),
TraceLoggingValue(m_id.c_str(), "Id"),
TraceLoggingValue((int)m_state, "OldState"),
TraceLoggingValue((int)m_state, "NewState"));
m_state = newState;
m_stateChangeEvent.SetEvent();
// Reset the event for the next state change.
m_stateChangeEvent = wil::shared_event{wil::EventOptions::None};
TraceLoggingValue((int)event, "Event"));
}
HRESULT WSLAContainer::Stop(int Signal, ULONG TimeoutMs)
@ -159,7 +118,14 @@ CATCH_RETURN();
WSLA_CONTAINER_STATE WSLAContainer::State() noexcept
{
std::lock_guard lock{m_stateLock};
std::lock_guard lock{m_lock};
// If the container is running, refresh the init process state before returning.
if (m_state == WslaContainerStateRunning && m_containerProcess->State() != WSLAProcessStateRunning)
{
m_state = WslaContainerStateExited;
m_containerProcess.reset();
}
return m_state;
}

View File

@ -44,14 +44,13 @@ public:
static Microsoft::WRL::ComPtr<WSLAContainer> Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM, ContainerEventTracker& tracker);
private:
void OnStateChange(ContainerEvent event);
WSLA_CONTAINER_STATE WaitForTransition(WSLA_CONTAINER_STATE expectedState);
void OnEvent(ContainerEvent event);
void WaitForContainerEvent();
std::optional<std::string> GetNerdctlStatus();
std::mutex m_lock;
std::mutex m_stateLock;
wil::shared_event m_stateChangeEvent{wil::EventOptions::None};
wil::unique_event m_startedEvent{wil::EventOptions::ManualReset};
std::optional<ServiceRunningProcess> m_containerProcess;
std::string m_name;
std::string m_image;

View File

@ -107,14 +107,17 @@ WSLASession::~WSLASession()
std::lock_guard lock{m_lock};
if (m_eventTracker.has_value())
{
m_eventTracker.reset();
}
// TODO: Stop containers.
m_containers.clear();
m_eventTracker.reset();
if (m_virtualMachine)
{
m_virtualMachine->OnSessionTerminated();
// TODO: Signal containerd to exit before umounting /root.
LOG_IF_FAILED(m_virtualMachine->Unmount("/root"));
m_virtualMachine.Reset();
}

View File

@ -107,6 +107,7 @@ Abstract:
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"wslg.exe") \
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"msrdc.exe") \
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"msal.wsl.proxy.exe") \
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"wslaservice.exe") \
END_TEST_CLASS()
//

View File

@ -1052,7 +1052,7 @@ class WSLATests
std::filesystem::remove_all(storagePath, error);
if (error)
{
LogError("Failed to cleanup storage path %ws: %s", storagePath.c_str(), error.message().c_str());
LogError("Failed to cleanup storage path %ws: %hs", storagePath.c_str(), error.message().c_str());
}
});
@ -1082,6 +1082,8 @@ class WSLATests
}
// Validate that starting containers works with the default entrypoint and content on stdin
// TODO: this hangs using nerdctl start -a
/*
{
WSLAContainerLauncher launcher(
"debian:latest", "test-default-entrypoint", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr);
@ -1115,7 +1117,7 @@ class WSLATests
auto process = container.GetInitProcess();
ValidateProcessOutput(process, {{1, ""}});
}
}*/
// Validate error paths
{
@ -1151,7 +1153,7 @@ class WSLATests
std::filesystem::remove_all(storagePath, error);
if (error)
{
LogError("Failed to cleanup storage path %ws: %s", storagePath.c_str(), error.message().c_str());
LogError("Failed to cleanup storage path %ws: %hs", storagePath.c_str(), error.message().c_str());
}
});