Skip to content

Wait for an orphaned coordinated build before building again - #21622

Open
David Fowler (davidfowl) wants to merge 4 commits into
mainfrom
aspireagent/wait-for-orphaned-coordinated-build
Open

David Fowler (davidfowl) wants to merge 4 commits into
mainfrom
aspireagent/wait-for-orphaned-coordinated-build

Conversation

@davidfowl

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

Copy link
Copy Markdown
Collaborator

Description

Fixes the CLI Starter Validation flake tracked on #21600, where aspire-starter: aspire wait for resource apiservice failed with exit code 18 (WaitResourceFailed).

Root cause

cli-starter-validation.ps1 runs aspire run --detach, then aspire stop right away, then aspire start and aspire wait apiservice. Three things combine to cause the failure:

  1. aspire stop returns as soon as the AppHost process exits. DCP (start-apiserver --monitor <dead pid>) cleans up its executables asynchronously after that.
  2. The coordinated __dotnet-project-build (dotnet build obj/.aspire/build/projects.<hash>.proj) ignores SIGTERM and runs to completion. Locally, kill -TERM did not stop it and it exited 0.
  3. The new AppHost started by aspire start launches a second dotnet build of the same projects in the same build directory while the orphaned build is still running.

The two builds then collide on shared intermediate outputs. The new build fails, WaitForSuccessfulBuildAsync fails apiservice/webfrontend, and aspire wait exits with code 18.

CI evidence (last 7 days, main and PRs):

Local repro:

  • After aspire stop returns success, ps still shows AppHost [main] Update dependencies from dotnet/arcade #1's DCP processes and its dotnet build ... projects.<hash>.proj.
  • I ran two overlapping dotnet build invocations of the same generated projects.proj across a sweep of start offsets. 2 of 11 runs failed with:
    error : The process cannot access the file 'Smoke.Web/obj/Debug/net10.0/compressed/<id>.gz' because it is being used by another process (StaticWebAssets Compression.targets(276,5))
  • Sequential builds never failed.

Fix

  • The coordinated build now writes an MSBuild file log into its build directory, with a per-AppHost-process name: -flp9:LogFile=<BuildDirectory>/logs/<pid>.<resource>.log;Verbosity=minimal. A build never opens a log that another build holds.
  • MSBuild's file logger holds that file open for writing for the whole lifetime of the build process. On Unix, .NET backs this with an flock (LOCK_SH). On Windows it's a sharing violation. A probe found the lock held in 62 of 62 checks during a build.
  • So an open handle on the log reliably means "a build is still running in this directory", even when the AppHost that launched it is gone.
  • Before launching, a new coordinated build tries to take an exclusive lock on every logs/*.log using the existing FileLock helper. Free logs are deleted when acquired.
    • If any log is held, it logs Waiting for a coordinated build that is still running in '...' to finish before building. and waits for it to be released.
    • The wait is bounded at 5 minutes. On timeout it logs a warning and builds anyway with its own log file, so behavior is never worse than today.
  • Once the previous build finishes, the new build is incremental.
  • Exclusions:
    • File-based app builds (dotnet build <file>.cs) are excluded because they reject MSBuild logger switches.
    • Build directories containing ; or = are excluded because MSBuild's file logger splits its parameters on both and can't express them.
  • No sleeps, retries or timeouts in tests or scripts were changed.

Tests

  • New test CoordinatedBuildWaitsForBuildStillRunningInSameBuildDirectory:

    1. Holds another build's log the way MSBuild does (FileShare.Read writer) and leaves a finished log alongside it.
    2. Asserts that argument evaluation for the coordinated build blocks and logs the waiting message.
    3. Releases the file and asserts the build proceeds with its own -flp9 log and the finished log was deleted.

    Without the fix, it fails (no wait happens, so the test times out with TaskCanceledException). With the fix, it passes.

  • New test CoordinatedBuildUsesItsOwnFileLogWhenRunningBuildOutlastsWait: when the wait times out, the build proceeds with its own log, which can be opened while the other build's log is still held.

  • New theory CoordinatedBuildOmitsFileLogWhenBuildDirectoryCannotBeALoggerParameter (; and =).

  • Updated coordinated-build argument expectations. File-app build expectations confirm the log is omitted.

  • Local run: DotnetProjectBuildCoordinatorTests + DotnetProjectResourceTests, 188/188 passed (including the real dotnet build/DCP file-app and restore tests).

E2E evidence

The CLI Starter Validation jobs exercise the real run --detach → stop → start → wait flow, which is the flow this flake hit. They all passed on the exact head 988d7f5579550a731ab9c92aee157c010ab53e20 (CI run 41256233278, 288/288 checks green, no reruns):

  • Starter Validation (Ubuntu x64): success
  • Starter Validation (Ubuntu ARM64): success
  • Starter Validation (Windows x64): success
  • Starter Validation (Windows ARM64): success
  • Starter Validation (macOS ARM64): success
  • Starter Validation (macOS x64): success
  • Cli.EndToEnd-StartStopTests (ubuntu-latest): success

`aspire stop` returns once the AppHost exits, but DCP cleans up asynchronously
and `dotnet build` ignores SIGTERM, so a coordinated build launched by the
previous AppHost can keep running. A fresh AppHost (`aspire start`) then
launches a second build of the same projects, and the two collide on shared
intermediate outputs (e.g. static web asset compression sharing violations),
failing the build and every dependent resource. This is the CLI Starter
Validation flake where `aspire wait apiservice` exits with code 18.

The coordinated build now writes an MSBuild file log into its build
directory. MSBuild holds that file open for the lifetime of the build
process, so before launching, a new coordinated build waits (bounded) until
it can take an exclusive lock on the log. File-based app builds are excluded
because `dotnet build <file>.cs` rejects logger switches.

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 -- 21622

Or

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

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

This comment has been minimized.

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.

🟡 Changes recommended

After the five-minute timeout, the new build still targets the locked build.log, causing logger initialization to fail on Windows instead of allowing the intended fallback to proceed.

1 open issue
What changed in this PR

This PR addresses the CLI Starter Validation flake tracked in #21600 by waiting for an orphaned coordinated .NET build before starting another build in the same directory.

Changes:

  • Uses an MSBuild file log to detect an active build, with a five-minute bounded wait.
  • Excludes file-based apps and build paths containing semicolons from file logging.
  • Adds waiting and exclusion tests, and updates expected build arguments.
File Description
tests/​Aspire.Hosting.Dotnet.Tests/​DotnetProjectBuildCoordinatorTests.cs Adds lock-wait and semicolon-path coverage; updates argument expectations.
src/​Aspire.Hosting.Dotnet/​DotnetProjectBuildResource.cs Defines the log path and bounded wait for an existing build.
src/​Aspire.Hosting.Dotnet/​DotnetProjectBuildCoordinator.cs Waits before eligible builds and adds the MSBuild logger argument.

Review effort: Balanced

The still-running build owns the log; on Windows a second file logger on it
fails logger initialization instead of building anyway.

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

Copy link
Copy Markdown
Collaborator Author

Cross-ref from #21621 (VS Code Windows browser-debugger standalone-gateway FailedToStart right after __dotnet-project-build-2): each failing run has a single AppHost launch, so the orphaned-AppHost-build overlap fixed here doesn't apply. Instead, in 3/3 failures the C# extension's Roslyn BuildHost ran a design-time load of the same projects during the coordinated -mt build (details: #21621 (comment)). It's the same overlapping-build family, but IDE design-time builds don't take the build.log lock, so I'll keep #21621 separate and start by making coordinated build failures visible in the AppHost logs. No competing change to this PR.

🤖 Posted by AspireAgent (AI Agent)

@github-actions

This comment has been minimized.

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.

🟡 Changes recommended

The lock is released before the child build starts, allowing concurrent AppHosts to use the same log file and cause overlapping builds on Unix or logger sharing violations on Windows.

1 open issue
1 resolved since last review

Review effort: Balanced

Each AppHost's coordinated build logs to logs/<pid>.<resource>.log and
waits for every log another build still holds, so a build that proceeds
after the wait timeout never opens a file logger on a held log.

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.

🔵 Needs a closer look

Build directories containing = are not excluded, causing MSBuild to truncate the log path, potentially fail logger initialization, and leave the running-build probe without its expected log.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Exclude build directories containing '=' from logger paths

src/​Aspire.Hosting.Dotnet/​DotnetProjectBuildResource.cs:230

Build directories containing = also need to be excluded: MSBuild's FileLogger.ParseFileLoggerParameters splits on every = and takes only the first value segment, so /work/repo=feature/... becomes /work/repo. This writes to the wrong file or fails logger initialization if that prefix is a directory, and leaves no log where the running-build probe expects it. Treat = like ; and extend the exclusion test to cover it.

1 resolved since last review

Review effort: Balanced

@github-actions

This comment has been minimized.

MSBuild's file logger splits each parameter on '=' and keeps only the
first value segment, so such a LogFile path would be truncated.

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

Copy link
Copy Markdown
Collaborator Author

Re: the Copilot review note about build directories containing =: confirmed. MSBuild's FileLogger.ParseFileLoggerParameters splits each parameter on = and keeps only the first value segment. Fixed in 988d7f5: build directories containing ; or = now get no file log (and so no running-build probe), the same as before this PR. The exclusion test is now a theory covering both characters.

🤖 Posted by AspireAgent (AI Agent)

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.

🟢 Approval recommended

No active review issues were identified for the changes that wait for orphaned coordinated builds before starting another build in the same directory.

No open issues 🎉

Review effort: Balanced

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Tests selector

20 / 100 PR test projects · 4 PR jobs, from 3 changed files.

Selected PR test projects (20 / 100)

Aspire.Cli.EndToEnd.Tests, Aspire.Hosting.Azure.EntraId.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Playground.Tests, Aspire.Templates.Tests

Selected PR jobs (4)

cli-starter-validation, extension-e2e, polyglot, typescript-api-compat


How these were chosen — grouped by what changed

⚠️ 20 of the 20 selected test projects come from a single change — src/Aspire.Hosting.Dotnet/DotnetProjectBuildCoordinator.cs.

🔧 src/Aspire.Hosting.Dotnet/DotnetProjectBuildCoordinator.cs (changed source)
→ 3 directly: Aspire.Cli.EndToEnd.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Templates.Tests
→ 17 via the project graph

show 17

Aspire.Hosting.Azure.EntraId.Tests, Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests (2 hops), Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.DotnetTool.Tests (2 hops), Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests (2 hops), Aspire.Playground.Tests (2 hops)

🔧 src/Aspire.Hosting.Dotnet/DotnetProjectBuildResource.cs (changed source)
→ 3 directly: Aspire.Cli.EndToEnd.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Templates.Tests

📦 affected project Aspire.Hosting.Dotnet
→ 2 tests: Aspire.Cli.EndToEnd.Tests, Aspire.Templates.Tests

🧪 tests/Aspire.Hosting.Dotnet.Tests/DotnetProjectBuildCoordinatorTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Dotnet.Tests

Job reasons

Job Triggered by
cli-starter-validation • src/Aspire.Hosting.Dotnet/DotnetProjectBuildCoordinator.cs, src/Aspire.Hosting.Dotnet/DotnetProjectBuildResource.cs
• affected project Aspire.Hosting.Dotnet
extension-e2e • src/Aspire.Hosting.Dotnet/DotnetProjectBuildCoordinator.cs, src/Aspire.Hosting.Dotnet/DotnetProjectBuildResource.cs
• affected project Aspire.Hosting.Dotnet
polyglot affected project Aspire.Hosting.Dotnet
typescript-api-compat affected project Aspire.Hosting.Dotnet

Selection computed for commit 988d7f5.

@davidfowl
David Fowler (davidfowl) marked this pull request as ready for review November 1, 2026 01:08
@davidfowl David Fowler (davidfowl) added the ci-health Fixes CI flakiness or CI infrastructure (AspireAgent queue) label Nov 1, 2026 — with Copilot SWE Agent

This branch has not been deployed

No deployments
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.

2 participants