Description
The boot-script goroutine spawned by provisionVM in pkg/driver/wsl2/vm_windows.go (lines 116–143) wraps a one-shot context-cancellation wait in an infinite for {} loop:
go func() {
cmd := exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "bash", "-c", limaBootFileLinuxPath)
out, err := cmd.CombinedOutput()
os.RemoveAll(limaBootFileWinPath)
...
for {
<-ctx.Done()
logrus.Info("Context closed, stopping vm")
if status, err := getWslStatus(ctx, instanceName); err == nil &&
status == limatype.StatusRunning {
_ = stopVM(ctx, distroName)
}
}
}()
Once ctx is canceled (the normal trigger for any limactl stop or hostagent shutdown), <-ctx.Done() returns instantly on every iteration. The goroutine then hot-loops:
- pinning a CPU core,
- spawning a fresh
wsl.exe subprocess on every iteration via getWslStatus (and, until the VM shows as stopped, via stopVM too),
- flooding
wslservice.exe, which is global on the Windows host and shared with every other WSL distro (Docker Desktop, Rancher Desktop, plain Ubuntu shells, etc.).
The hostagent process never exits cleanly because this goroutine never returns; ha.pid / ha.sock linger until the OS kills the hostagent.
Reproduction (deterministic)
limactl start --vm-type=wsl2 --name=demo template://default-windows
# ... wait for ready ...
limactl stop demo
Within ~1 second of the stop returning, the hostagent process pegs a CPU core and wsl.exe PIDs increment many times per second (visible in Process Explorer or Get-Process wsl polled in PowerShell). The hostagent does not exit on its own.
This is structural, not timing-dependent — the loop has no exit condition.
Fix
The intent was clearly "wait once, then stop the VM." Dropping the for { /} wrapper restores that. PR follows.
Environment
Affects all WSL2 instances (vmType: wsl2) on Windows. Found by code inspection against master.
Description
The boot-script goroutine spawned by
provisionVMinpkg/driver/wsl2/vm_windows.go(lines 116–143) wraps a one-shot context-cancellation wait in an infinitefor {}loop:Once
ctxis canceled (the normal trigger for anylimactl stopor hostagent shutdown),<-ctx.Done()returns instantly on every iteration. The goroutine then hot-loops:wsl.exesubprocess on every iteration viagetWslStatus(and, until the VM shows as stopped, viastopVMtoo),wslservice.exe, which is global on the Windows host and shared with every other WSL distro (Docker Desktop, Rancher Desktop, plain Ubuntu shells, etc.).The hostagent process never exits cleanly because this goroutine never returns;
ha.pid/ha.socklinger until the OS kills the hostagent.Reproduction (deterministic)
Within ~1 second of the stop returning, the hostagent process pegs a CPU core and
wsl.exePIDs increment many times per second (visible in Process Explorer orGet-Process wslpolled in PowerShell). The hostagent does not exit on its own.This is structural, not timing-dependent — the loop has no exit condition.
Fix
The intent was clearly "wait once, then stop the VM." Dropping the
for {/}wrapper restores that. PR follows.Environment
Affects all WSL2 instances (
vmType: wsl2) on Windows. Found by code inspection againstmaster.