Fix static web assets lock-race issue for E2E tests#68504
Conversation
Prevent concurrent E2E trimmed app publishes from writing the same static web asset compression intermediates. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR mitigates a Windows CI file-lock race in Components E2E trimmed app publishes by serializing project-reference traversal for the E2E test project when trimmed apps are being published.
Changes:
- Disable parallel project-reference builds/publishes (
BuildInParallel=false) whenTestTrimmedApps=trueto prevent concurrent writes to shared static web asset compression intermediates.
Show a summary per file
| File | Description |
|---|---|
| src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj | Serializes project-reference traversal for trimmed-app publish scenarios by setting BuildInParallel=false under TestTrimmedApps. |
Review details
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
| <TestTrimmedApps Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</TestTrimmedApps> | ||
| <TestTrimmedApps Condition="'$(Configuration)' == 'Release'">true</TestTrimmedApps> | ||
| <!-- The trimmed app publishes share project intermediate outputs, so their project references cannot run concurrently. --> | ||
| <BuildInParallel Condition="'$(TestTrimmedApps)' == 'true'">false</BuildInParallel> |
There was a problem hiding this comment.
Does this increase CI time? Do we want to ensure that apps use separate intermediate directories?
There was a problem hiding this comment.
Why does it happen that a single app is published in parallel (more than once) for a single build?
Isn't the underlaying issue in msbuild not reusing existing build/project instance?
Possibly by passing different props?
There was a problem hiding this comment.
Assessed; I don’t think a code change is needed beyond fc00a63. This can increase only the Release/CI Components E2E trimmed-app preparation because those trimmed app project references now run serially; Debug/local and product builds keep their existing parallelism.
Separate intermediate directories would need to partition the referenced framework project intermediates, not just each app's PublishDir, because the collision is under artifacts\obj\Microsoft.AspNetCore.Components.WebAssembly\Release\net11.0\compressed\publish\.... That would be a broader and likely more expensive workaround.
This isn't one app intentionally being published twice. The E2E project publishes multiple trimmed apps in parallel, and those nested publishes each traverse Microsoft.AspNetCore.Components.WebAssembly with the same effective Configuration/TFM/static-web-asset identity. MSBuild doesn't coalesce those nested Publish traversals across the parallel project-reference invocations, so serializing at the E2E project owner is the smallest scoped fix.
Summary
Serialize the Components E2E trimmed app project-reference traversal so concurrent publishes cannot write the same static web asset compression intermediate.
This PR was created after two Windows CI failures demonstrated that multiple trimmed Blazor WebAssembly app publishes can concurrently traverse the same shared framework project and race while producing its Brotli-compressed static assets.
Failure being addressed
Build 1552169 failed in Windows local development validation while publishing the Components E2E test applications.
GlobalizationWasmAppandWasm.Performance.TestAppoverlapped while both traversedMicrosoft.AspNetCore.Components.WebAssembly.csproj.Each traversal ran
GeneratePublishCompressedStaticWebAssetsfor the same logical_framework/blazor.webassembly.js.mapasset. Because the SDK derives the compressed output identity from the shared asset metadata, both invocations targeted the same file:The Brotli tool opens that output with
FileMode.Createand does not coordinate across separate MSBuild invocations, so one process failed when the other held the file open.The previous mitigation in #68463 avoided the redundant
Components.TestServertraversal, but it did not cover the direct trimmed app project references. Current-main build 1552404 reproduced the identical lock after #68463 merged, confirming the remaining ownership boundary.Correction
When
TestTrimmedApps=true, setBuildInParallel=falseinMicrosoft.AspNetCore.Components.E2ETests.csproj.This serializes the Release/CI project-reference traversal that owns these trimmed app publishes, preventing two callers from entering the shared compression path simultaneously. Debug and ordinary local builds retain their existing parallel behavior.
The fix intentionally does not add retries, sleeps, disable compression, or globally serialize product builds. It is scoped to the test project and configuration that creates the duplicate writers.
Validation
Property evaluation confirmed:
TestTrimmedApps=true,BuildInParallel=falseTestTrimmedAppsunset,BuildInParallel=trueA targeted Release build of
Microsoft.AspNetCore.Components.E2ETests.csprojsucceeded, and its binlog confirmed the E2EResolveProjectReferencesMSBuild task ran withBuildInParallel=False.The replacement PR CI also passed the relevant Windows behavioral validation:
aspnetcore-ciScope and limitations
This is a repository-owned workaround for the proven Components E2E publish topology. It does not make arbitrary concurrent SDK static web asset publishes safe; a systemic fix would belong in
dotnet/sdk.Local behavioral validation was performed on macOS, which cannot reproduce the same Windows file-sharing exception. The PR's passing Windows local development validation provides the platform-specific confirmation.