Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ba81b75
Add hourly upstream sync workflow for feature/wsl-for-apps
kvega005 Mar 5, 2026
50f9ce0
Remove upstream sync workflow
kvega005 Mar 5, 2026
c55426c
Merge branch 'microsoft:master' into master
kvega005 Mar 23, 2026
0055811
Merge branch 'microsoft:master' into master
kvega005 Apr 1, 2026
de88707
Merge branch 'microsoft:master' into master
kvega005 Apr 12, 2026
f16fc61
Merge branch 'microsoft:master' into master
kvega005 Apr 30, 2026
da31dd2
Merge branch 'microsoft:master' into master
kvega005 May 1, 2026
b95f237
Merge branch 'microsoft:master' into master
kvega005 May 1, 2026
2ae93aa
Merge branch 'microsoft:master' into master
kvega005 May 6, 2026
f805f05
Merge branch 'microsoft:master' into master
kvega005 May 7, 2026
f2b4090
Merge branch 'microsoft:master' into master
kvega005 May 13, 2026
07445a2
Merge branch 'microsoft:master' into master
kvega005 May 15, 2026
9a29435
Merge branch 'microsoft:master' into master
kvega005 May 18, 2026
ba3bc80
Merge branch 'microsoft:master' into master
kvega005 May 19, 2026
d11cbbc
Merge branch 'microsoft:master' into master
kvega005 May 21, 2026
eaae1bc
Merge branch 'microsoft:master' into master
kvega005 May 22, 2026
9f6a16c
Merge branch 'microsoft:master' into master
kvega005 May 27, 2026
dcaaccb
Merge branch 'master' of https://github.com/kvega005/WSL
Jun 11, 2026
119c17a
Merge branch 'microsoft:master' into master
kvega005 Jun 12, 2026
434d93c
Merge branch 'microsoft:master' into master
kvega005 Jun 18, 2026
00b370c
Merge branch 'microsoft:master' into master
kvega005 Jul 1, 2026
39c6c8f
Merge branch 'microsoft:master' into master
kvega005 Jul 6, 2026
c723316
Merge branch 'microsoft:master' into master
kvega005 Jul 7, 2026
42c14a0
Merge branch 'microsoft:master' into master
kvega005 Jul 9, 2026
363932b
Merge branch 'master' of https://github.com/kvega005/wsl
kvega005 Jul 15, 2026
f2a5245
Use transitions to manage container lifecycle operations
kvega005 Jul 22, 2026
5fa5aeb
Log on unexpected event
kvega005 Jul 22, 2026
e6e1a6e
Add assert
kvega005 Jul 22, 2026
1b31af0
Address feedback about vague code
kvega005 Jul 22, 2026
0afa44c
Add tests
kvega005 Jul 22, 2026
6cd6be8
Fix plugin test
kvega005 Jul 22, 2026
17109c6
Rename transition guard
kvega005 Jul 22, 2026
ef7ed22
Merge branch 'microsoft:master' into master
kvega005 Jul 23, 2026
3e981fa
Address feedback about disconnect deadlock and no throw in on event
kvega005 Jul 24, 2026
de2cac6
Allow transitions to outlive COM calls
kvega005 Jul 24, 2026
ffceff2
Fix deadlock
kvega005 Jul 27, 2026
9121724
Cleanup
kvega005 Jul 27, 2026
8b635b1
Merge branch 'microsoft:master' into master
kvega005 Jul 29, 2026
e5eeb15
Address feedback
kvega005 Jul 31, 2026
4b9e7f1
Fix concurrent start/stop/delete calls
kvega005 Jul 31, 2026
cdd88c4
Address feedback
kvega005 Aug 17, 2026
c0c8168
Merge branch 'microsoft:master' into master
kvega005 Aug 18, 2026
cb8cf61
Merge remote-tracking branch 'origin/master' into user/kevinve/contai…
kvega005 Aug 19, 2026
5e85eeb
Merge branch 'microsoft:master' into master
kvega005 Aug 19, 2026
7b21738
Merge remote-tracking branch 'origin/master' into user/kevinve/contai…
kvega005 Aug 19, 2026
49433d2
Fix typo
kvega005 Aug 20, 2026
1f47c48
Remove lifecycle lock from stop
kvega005 Aug 20, 2026
2052202
Update comments
kvega005 Aug 21, 2026
0dfea0f
Use state generation counter
kvega005 Aug 24, 2026
9dc421c
Merge branch 'microsoft:master' into master
kvega005 Aug 24, 2026
97ba916
Merge remote-tracking branch 'origin/master' into user/kevinve/contai…
kvega005 Aug 24, 2026
16a01c5
Address feedback
kvega005 Aug 24, 2026
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 64 additions & 31 deletions src/windows/wslcsession/DockerEventTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ void DockerEventTracker::OnEvent(const std::string_view& event)
void DockerEventTracker::OnContainerEvent(const nlohmann::json& parsed, const std::string& action, std::int64_t eventTime)
{
static std::map<std::string, ContainerEvent> events{
{"start", ContainerEvent::Start}, {"die", ContainerEvent::Stop}, {"destroy", ContainerEvent::Destroy}, {"exec_die", ContainerEvent::ExecDied}};
{"start", ContainerEvent::Start},
{"die", ContainerEvent::Stop},
{"destroy", ContainerEvent::Destroy},
{"exec_die", ContainerEvent::ExecDied},
{"restart", ContainerEvent::Restart}};

auto actor = parsed.find("Actor");
THROW_HR_IF_MSG(E_INVALIDARG, actor == parsed.end(), "Missing Actor in container event");
Expand Down Expand Up @@ -191,15 +195,23 @@ void DockerEventTracker::OnContainerEvent(const nlohmann::json& parsed, const st
}
}

std::lock_guard lock{m_lock};

for (const auto& e : m_containerCallbacks)
// Snapshot the matching callbacks so that they can be invoked without holding m_lock. Callbacks can register and
// unregister callbacks (a container that stops releases its exec processes), which would otherwise mutate the
// vector being iterated.
std::vector<std::shared_ptr<ContainerCallback>> callbacks;
{
if (e.ContainerId == containerId && (!e.ExecId.has_value() || e.ExecId == execId))
std::lock_guard lock{m_lock};

for (const auto& e : m_containerCallbacks)
{
e.Callback(it->second, exitCode, eventTime);
if (e->ContainerId == containerId && (!e->ExecId.has_value() || e->ExecId == execId))
{
callbacks.emplace_back(e);
}
}
}

InvokeCallbacks(callbacks, [&](const ContainerCallback& e) { e.Callback(it->second, exitCode, eventTime); });
}

void DockerEventTracker::OnVolumeEvent(const nlohmann::json& parsed, const std::string& action, std::int64_t eventTime)
Expand All @@ -220,12 +232,13 @@ void DockerEventTracker::OnVolumeEvent(const nlohmann::json& parsed, const std::

auto volumeName = id->get<std::string>();

std::lock_guard lock{m_lock};

for (const auto& e : m_volumeCallbacks)
std::vector<std::shared_ptr<VolumeCallback>> callbacks;
{
e.Callback(volumeName, it->second, eventTime);
std::lock_guard lock{m_lock};
callbacks = m_volumeCallbacks;
}

InvokeCallbacks(callbacks, [&](const VolumeCallback& e) { e.Callback(volumeName, it->second, eventTime); });
}

void DockerEventTracker::WaitForObjectCreated(const std::string& ObjectId)
Expand Down Expand Up @@ -257,50 +270,70 @@ void DockerEventTracker::WaitForObjectCreated(const std::string& ObjectId)
DockerEventTracker::EventTrackingReference DockerEventTracker::RegisterContainerStateUpdates(
const std::string& ContainerId, ContainerStateChangeCallback&& Callback) noexcept
{
std::lock_guard lock{m_lock};

auto id = m_callbackId++;
m_containerCallbacks.emplace_back(id, ContainerId, std::optional<std::string>{}, std::move(Callback));
auto entry = std::make_shared<ContainerCallback>(id, std::string{ContainerId}, std::optional<std::string>{}, std::move(Callback));

std::lock_guard lock{m_lock};
m_containerCallbacks.emplace_back(std::move(entry));

return EventTrackingReference{this, id};
}

DockerEventTracker::EventTrackingReference DockerEventTracker::RegisterExecStateUpdates(
const std::string& ContainerId, const std::string& ExecId, ContainerStateChangeCallback&& Callback) noexcept
{
std::lock_guard lock{m_lock};

auto id = m_callbackId++;
m_containerCallbacks.emplace_back(id, ContainerId, ExecId, std::move(Callback));
auto entry = std::make_shared<ContainerCallback>(id, std::string{ContainerId}, std::optional<std::string>{ExecId}, std::move(Callback));

std::lock_guard lock{m_lock};
m_containerCallbacks.emplace_back(std::move(entry));

return EventTrackingReference{this, id};
}

DockerEventTracker::EventTrackingReference DockerEventTracker::RegisterVolumeUpdates(VolumeEventCallback&& Callback) noexcept
{
std::lock_guard lock{m_lock};

auto id = m_callbackId++;
m_volumeCallbacks.emplace_back(id, std::move(Callback));
auto entry = std::make_shared<VolumeCallback>(id, std::move(Callback));

std::lock_guard lock{m_lock};
m_volumeCallbacks.emplace_back(std::move(entry));

return EventTrackingReference{this, id};
}

void DockerEventTracker::UnregisterCallback(size_t Id) noexcept
{
std::lock_guard lock{m_lock};
std::shared_ptr<CallbackRegistration> registration;

// Try container callbacks first.
auto containerRemove = std::ranges::remove_if(m_containerCallbacks, [Id](auto& entry) { return entry.CallbackId == Id; });
if (!containerRemove.empty())
{
WI_ASSERT(containerRemove.size() == 1);
m_containerCallbacks.erase(containerRemove.begin(), containerRemove.end());
return;
std::lock_guard lock{m_lock};

auto matches = [Id](const auto& e) { return e->CallbackId == Id; };

// Try container callbacks first, then volume callbacks.
if (auto container = std::ranges::find_if(m_containerCallbacks, matches); container != m_containerCallbacks.end())
{
registration = std::move(*container);
m_containerCallbacks.erase(container);
}
else
{
auto volume = std::ranges::find_if(m_volumeCallbacks, matches);
WI_ASSERT(volume != m_volumeCallbacks.end());

if (volume != m_volumeCallbacks.end())
{
registration = std::move(*volume);
m_volumeCallbacks.erase(volume);
}
}
}

// Then volume callbacks.
auto volumeRemove = std::ranges::remove_if(m_volumeCallbacks, [Id](auto& entry) { return entry.CallbackId == Id; });
WI_ASSERT(volumeRemove.size() == 1);
m_volumeCallbacks.erase(volumeRemove.begin(), volumeRemove.end());
}
if (registration)
{
// Wait for any in-flight invocation to complete so the callback can't run once this returns.
std::lock_guard invokeLock{registration->InvokeLock};
registration->Unregistered = true;
}
}
63 changes: 52 additions & 11 deletions src/windows/wslcsession/DockerEventTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum class ContainerEvent
{
Create,
Start,
Restart,
Stop,
Exit,
Destroy,
Expand Down Expand Up @@ -84,28 +85,68 @@ class DockerEventTracker
void OnContainerEvent(const nlohmann::json& parsed, const std::string& action, std::int64_t eventTime);
void OnVolumeEvent(const nlohmann::json& parsed, const std::string& action, std::int64_t eventTime);

struct ContainerCallback
// Callbacks are invoked without holding m_lock so that a callback can register or unregister callbacks, and so
// that a callback taking its own lock can't invert with a thread that registers a callback under that same lock.
struct CallbackRegistration
{
size_t CallbackId;
std::string ContainerId;
std::optional<std::string> ExecId;
ContainerStateChangeCallback Callback;
NON_COPYABLE(CallbackRegistration);
NON_MOVABLE(CallbackRegistration);

CallbackRegistration(size_t Id) noexcept : CallbackId(Id)
{
}

const size_t CallbackId;

// Held while the callback runs so it can't be invoked once UnregisterCallback() returned for it.
// N.B. Recursive so a running callback can unregister itself.
std::recursive_mutex InvokeLock;
_Guarded_by_(InvokeLock) bool Unregistered = false;
};

struct VolumeCallback
struct ContainerCallback : CallbackRegistration
{
size_t CallbackId;
VolumeEventCallback Callback;
ContainerCallback(size_t Id, std::string&& ContainerId, std::optional<std::string>&& ExecId, ContainerStateChangeCallback&& Callback) :
CallbackRegistration(Id), ContainerId(std::move(ContainerId)), ExecId(std::move(ExecId)), Callback(std::move(Callback))
{
}

const std::string ContainerId;
const std::optional<std::string> ExecId;
const ContainerStateChangeCallback Callback;
};

std::vector<ContainerCallback> m_containerCallbacks;
std::vector<VolumeCallback> m_volumeCallbacks;
struct VolumeCallback : CallbackRegistration
{
VolumeCallback(size_t Id, VolumeEventCallback&& Callback) : CallbackRegistration(Id), Callback(std::move(Callback))
{
}

const VolumeEventCallback Callback;
};

_Guarded_by_(m_lock) std::vector<std::shared_ptr<ContainerCallback>> m_containerCallbacks;
_Guarded_by_(m_lock) std::vector<std::shared_ptr<VolumeCallback>> m_volumeCallbacks;

// Invokes a snapshot of callbacks taken under m_lock, skipping registrations that have since been unregistered.
template <typename TCallback, typename TInvoke>
static void InvokeCallbacks(const std::vector<std::shared_ptr<TCallback>>& Callbacks, const TInvoke& Invoke)
{
for (const auto& e : Callbacks)
{
std::lock_guard invokeLock{e->InvokeLock};
if (!e->Unregistered)
{
Invoke(*e);
}
}
}

_Guarded_by_(m_lock) std::unordered_set<std::string> m_createdObjects;
_Guarded_by_(m_lock) wil::unique_event m_objectCreated { wil::EventOptions::ManualReset };

WSLCSession& m_session;
std::recursive_mutex m_lock;
std::mutex m_lock;
std::atomic<size_t> m_callbackId{0};
};
} // namespace wsl::windows::service::wslc
Loading