Skip to content

Pin Windows CI container runtime to Docker to stop DCP wslc probes (#21570) - #21608

Merged
Ankit Jain (radical) merged 1 commit into
mainfrom
fix/windows-wslc-probe
Nov 3, 2026
Merged

Ankit Jain (radical) merged 1 commit into
mainfrom
fix/windows-wslc-probe

Conversation

@davidfowl

@davidfowl David Fowler (davidfowl) commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Mitigates #21570 (Windows hosted runner "lost communication"), tracked in #21600.

Repo lever found: DCP 0.27 (bumped in #20874 on 2026-10-10) added WSLc support. When no container runtime is configured, DCP probes wslc.exe on every AppHost start. The current windows-latest images (windows-2025 and windows-2025-vs2026) install the latest WSL MSI. Since WSL 3.0, that MSI puts C:\Program Files\WSL\wslc.exe on PATH. So every DCP start on Windows CI now runs wslc version, wslc info and wslc network list, and DCP kills the last one early ("Context expired, stopping process"). actions/runner-images#14938 reports hosted Windows workers freezing during WSL2 provisioning on the same image family.

This PR sets ASPIRE_CONTAINER_RUNTIME=docker for Windows test jobs in run-tests.yml and for the Windows VS Code extension E2E jobs, using a GITHUB_ENV step that only runs on Windows. With an explicit runtime, DCP probes only that runtime. Docker is the only runtime these runners use, and DCP already chooses it today.

Evidence

7-day bounded analysis. Scope: main ci.yml attempt-1 jobs from 57 non-cancelled runs, about 19.4k jobs. 97 jobs carried the annotation "The hosted runner lost communication with the server".

  • 45 of 57 runs (79%) had at least one lost runner.

  • All 97 losses were on windows-latest. None were on Linux or macOS.

  • Every loss was in a test step: Run extension E2E tests 44, Run tests (Windows) 52, Run nuget dependent tests (Windows) 1.

  • Losses were declared 46–58 minutes into the job, 47 at the median. In a sample of 12 lost jobs, the test step had started 1–4 minutes into the job, so the worker stops about 45 minutes before GitHub declares the loss. These shards normally finish in minutes, and the MTP --timeout 20m never fires.

  • Losses happen only in projects that start DCP:

    Project (Windows) Lost / jobs
    VS Code E2E (runs aspire run) 44 / 1085
    Aspire.Hosting.Tests 16 / 399
    Hosting.JavaScript 7 / 57
    Hosting.DevTunnels, Hosting.DotnetTool, Hosting.Java 5 / 57 each
    Hosting.Dotnet 4 / 57
    Hosting.Azure, Hosting.Keycloak 3 / 57 each
    Package tests (Windows) 1 / 896
    Dashboard, Components, Azure client and other non-DCP projects 0 / 57 each
  • No resource pressure appears in Windows heartbeats: memory about 20% of 16 GB, C: about 80% with about 30 GB free.

DCP invokes wslc on CI. The uploaded DCP logs (logs-Hosting.JavaScript-windows-latest, run 40196754950) contain:

Running WSLC command  C:\Program Files\WSL\wslc.exe version --format json
Running WSLC command  C:\Program Files\WSL\wslc.exe info --format json
Running WSLC command  C:\Program Files\WSL\wslc.exe network list --no-trunc --format json
Context expired, stopping process...  wslc.exe

actions/runner-images installs the latest WSL MSI (Install-WSL2.ps1), and microsoft/WSL/msipackage/package.wix.in installs wslc.exe and adds it to PATH. windows-2022 does not install WSL2.

An explicit runtime stops the other probes. Local harness with DCP 0.27.2, putting fake docker, podman and wslc on PATH and logging each call:

  • dcp info probes docker --version and podman container ls. The wslc probe is Windows-only.
  • dcp info --container-runtime docker probes only docker --version.

Aspire passes ASPIRE_CONTAINER_RUNTIME to DCP as --container-runtime (DcpHost, DcpDependencyCheck).

Validation plan: this PR's Windows DCP logs should show no wslc.exe invocations, and the loss rate is compared across this PR's CI runs. Results will be posted below.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No. This is a workflow-only change. Validation is CI evidence: DCP logs and loss rate. actionlint passes. No tests are disabled and no retries are added.
  • Did you add public API?
    • Yes
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No

🤖 Posted by AspireAgent (AI Agent)

DCP 0.27 probes wslc.exe (shipped by the WSL MSI on windows-2025 images) on
every AppHost start when no container runtime is configured. Windows runner
losses (#21570) occur only in jobs that launch DCP.

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

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21608

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21608"

@github-actions github-actions Bot added the area-engineering-systems infrastructure helix infra engineering repo stuff label Oct 31, 2026
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 31, 2026 18:21
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

Selects the full PR test matrix + all PR-gated jobs (ALL) — a rule matching '.github/workflows/run-tests.yml' selects ALL


Selection computed for commit 4b791db.

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.

🔵 Needs a closer look

Source review confirms configuration forwarding, but Windows-hosted validation is still needed to verify that DCP stops probing wslc.exe and runner-loss rates improve.

No open issues 🎉

What changed in this PR

Pins the container runtime to Docker for Windows CI tests to avoid DCP’s automatic WSLc probes, targeting the hosted-runner communication losses tracked in #21570.

Changes:

  • Sets ASPIRE_CONTAINER_RUNTIME=docker through GITHUB_ENV before Windows .NET and VS Code extension E2E tests.
  • Documents the workaround and links the tracking issue.
  • Leaves Linux and macOS runtime selection unchanged.
File Description
.github/​workflows/​run-tests.yml Adds a Windows-only Docker runtime setting for shared test jobs.
.github/​workflows/​extension-e2e-tests.yml Adds the same setting before Windows extension E2E tests.

Review effort: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Copy link
Copy Markdown
Collaborator Author

Attempt 1 of the CI run on head 4b791dbd finished with two failures. Both are known Windows VS Code E2E flakes that predate this change, and neither is a runner loss: both jobs ran to a normal test failure in minutes.

  • VS Code extension E2E (Windows, tree-actions): "Timed out after 10000ms waiting for E2E control revision". This is sig-0020 in the E2E health ledger VS Code extension E2E health ledger #19282, where windows/tree-actions passes 94.6%.
  • VS Code extension E2E (Windows, browser-debugger): "Timed out waiting for resource 'standalone-gateway' state Running" (gateway FailedToStart). Main has the same signature in job 124583752045 on 2026-10-31. Over the last 7 days, main had 5 failures in 55 windows/browser-debugger jobs. Before this change DCP already selected docker on these runners ("Runtime":"docker","Running":true in the main DCP logs), and the gateway is a project resource, not a container.

Repo automation has started a single rerun of the failed jobs (attempt 2). That is the one documented rerun for each of these flakes.

After-fix check: the DCP logs in logs-Hosting.DotnetTool-windows-latest from this run contain only Running Docker command entries (14) and no wslc or podman invocations. On main, every DCP start runs wslc.exe version/info/network list.

🤖 Posted by AspireAgent (AI Agent)

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Copy link
Copy Markdown
Collaborator Author

Rerun batch for loss-rate evidence. I ran a full rerun (attempt 3) of the same head 4b791dbd to get a second sample of Windows jobs.

Sample Windows jobs Lost runners
main ci.yml attempt 1, last 7 days (57 runs) 7,965 97 (1.2%), in 45 of 57 runs
This PR, attempt 1 144 0 (2 failures were test failures, see above)
This PR, attempt 3 (full rerun) 144 0 (144 of 144 succeeded)

At the baseline rate, about 3.5 losses would be expected across 288 jobs. Seeing 0 has a Poisson probability of about 3%. Post-merge monitoring on main is the definitive check.

Attempt 3 had one unrelated Linux failure: Hosting.MongoDB (ubuntu-latest) VerifyMongoDBMultiNodeReplicaSetAllNodesEndUpHealthy, the known replica-set flake #20478. Repo automation queued its single documented rerun (attempt 4).

🤖 Posted by AspireAgent (AI Agent)

Copy link
Copy Markdown
Collaborator Author

7-day runner-loss census. Scope: ci.yml, attempt 1, runs created 2026-10-25T15:10Z to 2026-11-01T15:10Z. A job counts as lost when it concluded failure while a step was still in_progress. That rule matched 31 of 31 annotation-confirmed losses in a 48h subset, with 0 false positives.

Scope Lost jobs Windows jobs (denominator) Per-job rate Runs hit
main (62 decided runs) 106 Windows + 1 Linux 8,676 1.22% 49 / 62
PRs (501 decided runs) 74 Windows + 1 Linux ~23k (estimated from a 60-run sample: 46.2 Windows jobs/run; sample rate 16/2,772 = 0.58%) ~0.3–0.6% 44 / 501

The new findings since the last evidence comment:

  • Still happening on main. After 2026-10-31T19:48Z there were 9 more Windows losses in 4 main runs (41255992064, 41260183978, 41266341036, 41283516743). 41260183978 also lost one Linux runner (VS Code extension E2E (Linux, browser-debugger)), which this PR does not cover.
  • No hot shard. Losses are spread over about 40 Windows shards; the largest is 11 jobs (VS Code E2E debug-startup-timeout), and every one starts DCP or runs aspire run. Losses were declared 46–110 minutes into the job (median 47). Logs for lost jobs return BlobNotFound, so there are no last log lines to inspect. Splitting shards would only add exposure, and lowering timeout-minutes would shorten the wait without changing the pass rate.
  • Runner loss is necessary but not sufficient for green main. Of 61 failed main runs, 5 failed only because of runner loss, and 44 had runner loss plus test failures. So this PR alone would move main attempt 1 from 1/62 to at most 6/62. In the last 48h (18 decided runs), it would move main to 14/18 only together with the ready test-fix PRs.

🤖 Posted by AspireAgent (AI Agent)

@radical
Ankit Jain (radical) merged commit 50bec8f into main Nov 3, 2026
1782 of 1789 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 17.0 milestone Nov 3, 2026
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

✅ No documentation update needed.

docs_optional → build_or_ci_only. Triggered signal: pr_body_has_cli_flag_mention (evidence: PR body mentions the MTP --timeout 20m test runner flag, not a product flag). Both changed files match .github/workflows/** (extension-e2e-tests.yml, run-tests.yml); no user-facing surface changed.

Ella Hathaway (ellahathaway) pushed a commit that referenced this pull request Nov 4, 2026
)

## Description

Windows runner loss and hangs are the largest flake family in #21600:
Windows extension E2E and .NET test jobs freeze at 47–59 minutes against
a 3–9 minute baseline, then lose their runner (#21570). We can't
currently answer whether the machines are overloaded on CPU, memory or
disk, because nothing records machine load in a form that survives the
job.

This PR adds a lightweight background resource sampler to:
- the Windows .NET test jobs (`run-tests.yml`)
- the Windows VS Code extension E2E shards (`extension-e2e-tests.yml`)

**What it records** (`eng/scripts/ci-resource-monitor.ps1`): a detached,
below-normal-priority `pwsh` process starts right after checkout. Every
20s it writes:
- `resources.csv`: CPU %, processor queue length, available MB, commit
charge % / committed GB / commit limit, paging pages/s, page file usage
%, disk queue length, disk % time, free GB on the system and workspace
drives, and process count.
- `processes.log`: the top 5 processes by CPU (since the previous
sample) and by working set (with private bytes).

**Where it goes:** the sampler writes into directories that the existing
`if: always()` uploads already collect, so there are no new artifacts:
- .NET tests: `artifacts/log/test-logs/resource-monitor/` inside
`logs-<test>-windows-latest`.
- Extension E2E: `extension/.test-results/resource-monitor/` inside
`extension-e2e-diagnostics-win-x64-<shard>-attempt<N>`.

A `Stop resource monitor (Windows)` step (`if: always()`) kills the
sampler before the upload. It prints a peak/minimum table, including the
largest gap between samples (a stall indicator), to the log and the job
summary. This covers passing, failing, cancelled and timed-out jobs.

**Safety:**
- Every mode exits 0 and both steps are `continue-on-error`.
- Test commands, timeouts, retries and environment are unchanged.
- Linux and macOS are untouched.
- Overhead is one `Get-Counter` and one `Get-Process` call every 20s at
below-normal priority.

**Limitation:** a runner that loses communication uploads nothing and
keeps no logs. This telemetry can't capture the final minutes of a lost
runner. It can show whether pressure builds on the slow jobs that
survive and on the shards that typically get lost. That is still more
than we have today: the existing console Heartbeat (60s, .NET tests
only) vanishes with the log.

Contributes to #21570. It complements #21608 (Docker pin), which adds no
telemetry.

### Existing evidence (before this PR)

On main, the last 16 completed `ci.yml` push runs had 1,551 Windows
jobs. All 20 jobs that ran 45 minutes or longer were runner-lost; job
logs return `BlobNotFound`, so there's no Heartbeat data from any of
them. Examples: [extension E2E resource-debug-tools,
110m](https://github.com/microsoft/aspire/actions/runs/41283516743/job/124783549300)
and [Hosting-4,
59m](https://github.com/microsoft/aspire/actions/runs/41168649739/job/124441989110).

Surviving Windows Hosting-6 jobs on main
([example](https://github.com/microsoft/aspire/actions/runs/41215270228/job/124578892630))
show the following in Heartbeat:
- memory 3.1–3.7 / 16.0 GB (20–23%)
- CPU 60–80% during test execution
- C: about 29 GB free, D: about 140 GB free

None of this shows exhaustion, but it doesn't cover commit charge, page
file or disk queue.

### Validation

-
`tests/Infrastructure.Tests/PowerShellScripts/CiResourceMonitorTests.cs`:
  - the summary math (peaks, minimums, averages, stall gap)
  - Stop with no samples
  - real Start → background sample → Stop that ends the sampler
- `actionlint` is clean on both workflows.
- This PR's own Windows CI run: results below.

### Observed in this PR's CI

The data comes from three CI runs of this PR on Windows hosted runners:
[41289502805](https://github.com/microsoft/aspire/actions/runs/41289502805),
[41290605859](https://github.com/microsoft/aspire/actions/runs/41290605859)
(both partly cancelled by later pushes) and
[41291481771](https://github.com/microsoft/aspire/actions/runs/41291481771)
(head `d0358d42`). It covers **331 Windows jobs with 2,923 samples**:
312 .NET test jobs and 19 VS Code extension E2E shards. The sampler
emitted no missing-counter warnings.

| | .NET test jobs (312) | Extension E2E shards (19) |
|---|---|---|
| CPU: mean of per-job averages | 54.8% | 44.3% |
| CPU: share of samples at 90% or more | 27.6% | 6.5% |
| Jobs with at least half their samples at 90% or more CPU | 44 | 0 |
| Lowest available memory (of 16 GB) | 9,891 MB | 9,779 MB |
| Peak commit charge (limit about 18.9 GB) | 33.4% | 36.6% |
| Peak page file usage | 22% | 19.9% |
| Peak disk queue length | 146.5 (one job; 4 jobs above 60) | 28.7 |
| Lowest free space on C: | 25.6 GB | 23.5 GB |
| Largest gap between samples | 37 s | 22 s |

Examples:
- [Extension E2E shard
`discovery-configuration`](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124809523820):
CPU 35% average (66% peak), at least 10.9 GB available, commit at most
26.7%, disk queue at most 11.6. The diagnostics artifact
`extension-e2e-diagnostics-win-x64-discovery-configuration-attempt1`
contains `resource-monitor/resources.csv` and `processes.log`.
-
[Hosting.Dotnet](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124805517960):
CPU 84.5% average with 10 of 16 samples at 90% or more. The
`logs-Hosting.Dotnet-windows-latest` artifact contains
`resource-monitor/`. Its `processes.log` shows `VBCSCompiler`, `dotnet`
and `MSBuild` as the top CPU consumers, i.e. the test-driven project
builds.
- [Cancelled
Hosting-4](https://github.com/microsoft/aspire/actions/runs/41289502805/job/124798187412):
Stop and upload still ran (CPU 82% average, disk queue at most 17.9).
- The sampler's own `pwsh` uses about 1% CPU in `processes.log`.

**Runner loss was reproduced in this run, and those jobs stayed dark.**
On head `d0358d42`, three .NET jobs froze in `Run tests (Windows)` at
about 09:15Z:
[Hosting.Java](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124805518377)
(2-minute baseline),
[Hosting.Browsers](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124805521155)
(3-minute baseline) and
[Hosting-4](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124805533340)
(8-minute baseline). MTP's `--timeout 20m` never fired. All three were
declared lost about 46 minutes after job start: *"The hosted runner lost
communication with the server"*, logs `BlobNotFound`, and no artifacts.
The [extension E2E `winui-debug`
shard](https://github.com/microsoft/aspire/actions/runs/41291481771/job/124809523893)
(5–6-minute baseline) froze the same way in `Run extension E2E tests`
from 09:34Z and was lost about 47 minutes after job start. This matches
main: in 9 of the last 16 `ci.yml` push runs, 1–3 Windows jobs per run
were lost at 46–48 minutes. In this PR's two earlier runs the same Java
and Browsers jobs, also running the sampler, finished in 1–3 minutes.

**What this answers so far:**
1. **Memory and commit:** not under pressure on any surviving Windows
job. At least 9.8 GB was always available, commit charge stayed at or
below 37%, and the page file at or below 22%.
2. **Disk:** space is fine (at least 23.5 GB free on C:, about 216 GB on
D:). The disk queue spikes occasionally (above 60 in 4 of 331 jobs),
during builds.
3. **CPU:** the 4-vCPU runners are often saturated during .NET test
jobs, mostly by the builds that tests trigger. Extension E2E shards are
moderate. No surviving job showed a stall: the largest gap between
samples was 37s against a 20s interval.
4. **Frozen jobs:** they freeze within minutes of starting tests, and
even the 2-minute Java suite froze. Telemetry can't see inside them,
because a lost runner uploads nothing. So the data rules out gradual
memory or disk exhaustion on the jobs we can observe, but can't confirm
or exclude a sudden in-VM event. Capturing that would need an off-box
sink, such as streaming samples to an external store. That is out of
scope for this lightweight change.

## Checklist

- Is this feature complete?
  - [x] Yes. Ready to ship.
  - [ ] No. Follow-up changes expected.
- Are you including unit tests for the changes and scenario tests if
relevant?
  - [x] Yes
  - [ ] No
- Did you add public API?
  - [ ] Yes
  - [x] No
- Does the change make any security assumptions or guarantees?
  - [ ] Yes
  - [x] No

🤖 Posted by AspireAgent (AI Agent)

---------

Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-engineering-systems infrastructure helix infra engineering repo stuff ci-health Fixes CI flakiness or CI infrastructure (AspireAgent queue)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants