Skip to content

wslc: idle-terminate per-user session VMs when inactive#40781

Open
benhillis wants to merge 21 commits into
masterfrom
user/benhill/wslc-idle-terminate-vm
Open

wslc: idle-terminate per-user session VMs when inactive#40781
benhillis wants to merge 21 commits into
masterfrom
user/benhill/wslc-idle-terminate-vm

Conversation

@benhillis

@benhillis benhillis commented Jun 11, 2026

Copy link
Copy Markdown
Member

Fixes #40961

Summary

Idle-terminates a per-user WSLC session's backing VM when it has been inactive, freeing memory while the session object (and its persistent storage) lives on. The VM is transparently recreated on the next operation.

Builds on #40770 (IWSLCVirtualMachineFactory).

Behavior

  • Only sessions with persistent storage (StoragePath set) idle-terminate.
  • An idle worker thread tears the VM down after a grace period (currently 30s) once there is no in-flight activity and no active container lock.
  • In-flight work holds an activity reference so the VM cannot be torn down mid-operation:
    • VmLease wraps CLI/container operations.
    • BeginContainerOperation hands clients an activity token (IFastRundown so a client crash reclaims it promptly).
    • Long-lived root-namespace processes (e.g. plugin hosts) created via CreateRootNamespaceProcess hold a keep-alive token for their lifetime.
  • Activity bookkeeping (count + wake event) lives in a shared IdleState held via shared_ptr, decoupled from the session's lifetime, so a held token suppresses idle teardown without extending the session object's lifetime (preserving the explicit-reset-invalidates-held-processes invariant from Add WSLC (WSL Containers) feature #40366).

VM plugin hooks

Idle-terminate breaks the old "session == running VM" assumption (the VM is lazily created, idle-terminated, and transparently recreated while the session lives on), so plugins gain VM-level hooks that fire on every VM (re)start/teardown, alongside the existing per-session OnSessionCreated/OnSessionStopping hooks:

  • WSLPluginAPI_OnWslcVmStarted / WSLPluginAPI_OnWslcVmStopping (WslPluginApi.h) with WslcVmStarted/WslcVmStopping fields in WSLPluginHooksV1.
  • IWSLCPluginNotifier::OnVmStarted() / OnVmStopping() (new methods, no new IID — service and per-user session ship together).
  • OnWslcVmStarted fires after releasing m_lock in EnsureVmRunning (the sole caller holds an activity reference across the gap, so idle teardown can't race the VM down; firing under the lock could deadlock on a reentrant WSLCCreateProcess).
  • OnWslcVmStopping fires under the lock in TearDownVmLockHeld, with fire-once pairing via std::atomic<bool> m_vmStartNotified so a failed bring-up emits no spurious stopping.

Testing

  • New WSLCE2EVmIdleTests E2E suite (5 tests) including WSLCE2E_VmIdle_RootProcessKeepsVmAlive.
  • New PluginTests::WslcVmRestart asserting the hook sequence: Session created -> VM started -> VM stopping -> VM started -> VM stopping -> Session stopping.
  • WSLCTests::CreateRootNamespaceProcess still passes.
  • Full x64 Debug build clean.

Notes / follow-ups (deferred)

  • Grace period is a hardcoded constexpr; making it injectable would enable deterministic race tests.
  • No crash-path (client dies holding token) automated coverage yet.

Note

Draft for early review.

Copilot AI review requested due to automatic review settings June 11, 2026 19:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds on-demand creation and idle-termination of per-user WSLC session VMs (for sessions with persistent storage), so memory can be reclaimed while keeping the session object and storage intact. It also introduces VM-liveness/activity bookkeeping to prevent teardown during in-flight operations and adds new E2E coverage around VM lifecycle behavior.

Changes:

  • Implement lazy VM bring-up and idle shutdown in wslcsession via an idle worker, activity counting/tokens, and a VmLease used by VM-requiring operations.
  • Add client-side “operation keep-alive” usage in wslc.exe container operations to prevent VM teardown between OpenContainer and subsequent calls/streaming.
  • Add a new E2E test suite validating lazy start, idle stop, persistence across restarts, keep-alive for root-namespace processes, and teardown/recreate races.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/windows/wslc/e2e/WSLCE2EVmIdleTests.cpp New E2E tests covering lazy VM start, idle stop, persistence, keep-alive, and race scenarios.
test/windows/wslc/e2e/WSLCE2EHelpers.h Exposes the underlying IWSLCSession* for diagnostics/test-only calls.
src/windows/wslcsession/WSLCSession.h Adds VM lifecycle state, idle worker/tokens/lease declarations, and new session methods.
src/windows/wslcsession/WSLCSession.cpp Implements lazy VM creation, idle teardown, activity tokens, and VM diagnostics reporting.
src/windows/wslcsession/WSLCProcessControl.cpp Preserves a real exit code when signaling container release, only synthesizing SIGKILL when needed.
src/windows/wslcsession/WSLCProcess.h Stores a keep-alive token on root-namespace processes to keep the VM alive for their lifetime.
src/windows/wslcsession/WSLCContainer.cpp Signals idle re-checks on terminal container transitions; holds a VM lease during delete.
src/windows/wslcsession/IORelay.h Adds IsRelayThread() to safely avoid destroying the relay on its own thread.
src/windows/wslcsession/IORelay.cpp Co-initializes the relay thread into the MTA; implements IsRelayThread().
src/windows/wslc/services/SessionModel.h Adds a helper to acquire/hold a keep-alive token for client-side container operations.
src/windows/wslc/services/ContainerService.cpp Uses the keep-alive token across container operations (attach/start/stop/kill/delete/exec/etc.).
src/windows/service/inc/wslc.idl Adds VM diagnostics type + new session methods for diagnostics and operation keep-alive.
src/windows/service/exe/WSLCSessionManager.cpp Updates comments to reflect on-demand VM creation and recreation after idle termination.

Comment thread src/windows/wslcsession/WSLCSession.h
Comment thread src/windows/service/inc/wslc.idl Outdated
Copilot AI review requested due to automatic review settings June 11, 2026 20:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread src/windows/wslcsession/WSLCSession.cpp Outdated
Comment thread src/windows/wslcsession/WSLCSession.cpp Outdated
@benhillis benhillis force-pushed the user/benhill/wslc-idle-terminate-vm branch from c12d7e1 to fa2eb47 Compare June 12, 2026 01:28
Copilot AI review requested due to automatic review settings June 12, 2026 17:46
@benhillis benhillis force-pushed the user/benhill/wslc-idle-terminate-vm branch from fa2eb47 to ea2254c Compare June 12, 2026 17:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Comment thread src/windows/service/inc/wslc.idl
Comment thread src/windows/service/inc/wslc.idl
Comment thread src/windows/service/inc/wslc.idl Outdated
Copilot AI review requested due to automatic review settings June 12, 2026 20:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.

Comment thread src/windows/service/inc/wslc.idl
Comment thread src/windows/service/inc/wslc.idl
Comment thread src/windows/WslcSDK/wslcsdk.def
Comment thread src/windows/WslcSDK/winrt/Session.cpp
@benhillis benhillis force-pushed the user/benhill/wslc-idle-terminate-vm branch from b870044 to 4bcd87f Compare June 17, 2026 00:22
Copilot AI review requested due to automatic review settings June 17, 2026 16:21
@benhillis benhillis force-pushed the user/benhill/wslc-idle-terminate-vm branch from 4bcd87f to 348e2e1 Compare June 17, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread test/windows/WSLCTests.cpp
Comment thread test/windows/WSLCTests.cpp

@benhillis benhillis left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed VM-related comments - all have been addressed:

Comment on WSLCSession.h:84: Already correct - lines 77-78 say "IWSLCVirtualMachineFactory" and "lazily on first use"

Comment on WSLCSession.cpp:571: Fixed by AddRef/Release activity tracking - idle worker checks ActivityCount (line 779), and container proxies increment it on AddRef 1→2 transition. VM will not idle-terminate while clients hold container proxies. See lines 672-674 comment.

Comment on WSLCSession.cpp:380: Already has exception handling - IdleWorker() is wrapped in CATCH_LOG() at lines 375-379

Comment on Session.cpp:65: Already correct - wil::unique_threadpool_wait (line 56) calls WaitForThreadpoolWaitCallbacks in destructor automatically

Copilot AI review requested due to automatic review settings June 17, 2026 16:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 14 comments.

Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread test/windows/WSLCTests.cpp
Comment thread src/windows/service/exe/HcsVirtualMachine.cpp Outdated
Comment thread test/windows/wslc/e2e/WSLCE2EVmIdleTests.cpp Outdated
Comment thread test/windows/wslc/e2e/WSLCE2EVmIdleTests.cpp Outdated
Copilot AI review requested due to automatic review settings June 17, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread test/windows/WSLCTests.cpp
Comment thread test/windows/WSLCTests.cpp
Copilot AI review requested due to automatic review settings June 17, 2026 21:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 12 comments.

Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread src/windows/service/inc/wslc.idl Outdated
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread src/windows/wslc/services/ContainerService.cpp
Comment thread test/windows/WSLCTests.cpp
Copilot AI review requested due to automatic review settings June 18, 2026 16:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/WSLCSessionRuntime.cpp
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy the termination reason/details into the session-visible fields before
SetEvent() so a waiter woken by the terminated event cannot observe the
default Unknown reason. Restores the pre-refactor ordering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 01:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/WSLCSession.cpp
…factories

Add a test-only TriggerIdleTermination COM method to force VM idle
teardown on demand, with three TAEF tests that hammer the VM
idle-termination race paths. Refactor WSLCContainer construction to
take WSLCSessionRuntime& instead of five individual runtime members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread test/windows/WSLCTests.cpp
The TriggerIdleTerminationRecoversRunningContainer test left the
persisted container in the shared default session on early-exit or if
the recovered handle's best-effort delete failed, making later
ListContainers-based tests order-dependent. Add a scope_exit that
force-deletes the container by name regardless of exit path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/CMakeLists.txt
Ben Hillis and others added 2 commits July 9, 2026 11:32
…dle-terminate-vm

# Conflicts:
#	src/windows/wslcsession/WSLCContainer.cpp
#	src/windows/wslcsession/WSLCSession.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/WSLCSession.cpp
Ben Hillis and others added 3 commits July 9, 2026 11:50
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/WSLCSessionRuntime.cpp Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Session-level WSLC plugin hooks (OnSessionCreated/OnSessionStopping)
assumed session == running VM. With idle termination (PR #40781) the VM
is created lazily, torn down when idle, and transparently recreated while
the session persists, so plugins had no notification of actual VM
lifecycle.

Add VM-level hooks OnWslcVmStarted/OnWslcVmStopping that fire on every VM
(re)start and teardown, in addition to the once-per-session hooks. Both
are best-effort (errors logged and ignored). OnWslcVmStarted fires after
releasing the runtime exclusive lock so a plugin may reentrantly call back
into the session (e.g. WSLCCreateProcess) without deadlocking;
OnWslcVmStopping fires under the lock during teardown, gated on a
fire-once flag so a failed bring-up emits no spurious stopping.

Adds a PluginTests::WslcVmRestart case that drives first start, forced
idle teardown (TriggerIdleTermination), and lazy restart, asserting the
hook sequence and proving reentrancy is deadlock-free.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f1c36ca0-19d0-46a7-82b3-10f1e1ee3e4c

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 1 comment.

Comment thread src/windows/wslcsession/WSLCSessionRuntime.cpp
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@benhillis

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

m_dockerClient(runtime.Docker()),
m_eventTracker(runtime.Events()),
m_ioRelay(*runtime.Relay()),
m_containerEvents(runtime.Events().RegisterContainerStateUpdates(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_containerEvents keeps a reference to the DockerEventTracker it was created with, but if that object goes away, it will operate on a dangling pointer.

The easiest way I can think of to solve this would be to keep the same event tracker object between different VM's, and have logic to "restart" it when a new VM starts (other wise, all containers & processes would need to re-register with the new event tracker)

// the lock, so the handler must not call back into the session.
if (m_vmStartNotified.exchange(false) && m_hooks.OnVmStopping)
{
m_hooks.OnVmStopping();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a potential deadlock here. If a plugin receives a "VM stopping" notification and runs an operation that can try to acquire a VM lease, we could get stuck on m_lock in VmLease's constructor

// Wait for dockerd to be ready before starting the event tracker.
THROW_WIN32_IF_MSG(
ERROR_TIMEOUT, !m_dockerdReadyEvent.wait(Settings->BootTimeoutMs), "Timed out waiting for dockerd to start");
m_containers.clear();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can do this unfortunately, because this will break any COM references that callers might have to containers


// Tear down the VM (if running) and all VM-scoped state, capturing the termination reason.
// This mirrors the soft teardown used for idle shutdown, but here it is permanent.
TearDownVmLockHeld(/* CaptureTerminationReason */ true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The locking is confusing me a bit here, because we're calling TearDownVmLockHeld, but we don't actually hold m_lock, only the session lock.

I think the locking still works because StopVmLockHeld uses an std::atomic to transition the VM to stopped, but I think we should make the locking a bit more clear here

ULONG Id{};
std::wstring DisplayName;
const std::atomic<bool>* Terminating{};
wil::unique_event* SessionTerminatingEvent{};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We could use wil::shared_event to avoid having to deal with pointers here

return S_OK;
}

g_logfile << "WSLC VM stopping, session=" << Session->SessionId << std::endl;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend launching a process inside the VM & removing a mount here, to validate that we handle these calls correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vmmemwslc-cli remains running after all containers have exited

3 participants