Fix CI flakiness: MSB4216 task host failures, dotnet-watch hangs, NuGet errors#53424
Fix CI flakiness: MSB4216 task host failures, dotnet-watch hangs, NuGet errors#53424
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens Helix/CI test execution for Hot Reload / dotnet-watch scenarios by reducing hang risk and improving the reliability of Helix environment setup.
Changes:
- Replace “effectively infinite” DCP/Aspire timeouts used by watch-based tests with bounded (5-minute) values to prevent multi-hour Helix hangs.
- Improve test process cleanup by closing stdin prior to termination and adding a bounded wait for process exit.
- Update Helix test entrypoint scripts to set
DOTNET_HOST_PATHand make NuGet source removal resilient when sources are absent.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/Microsoft.DotNet.HotReload.Test.Utilities/WatchableApp.cs | Switches DCP/Aspire-related environment timeouts to bounded values for CI. |
| test/Microsoft.DotNet.HotReload.Test.Utilities/AwaitableProcess.cs | Adjusts disposal/termination behavior to reduce hangs during cleanup. |
| build/RunTestsOnHelix.sh | Sets DOTNET_HOST_PATH and makes dotnet nuget remove source tolerant of missing sources. |
| build/RunTestsOnHelix.cmd | Sets DOTNET_HOST_PATH and suppresses errors when removing non-existent NuGet sources. |
You can also share your feedback on Copilot code review. Take the survey.
| catch (OperationCanceledException) | ||
| { | ||
| Logger.Log($"Process {Id} did not exit within 30 seconds after Kill()"); |
There was a problem hiding this comment.
If the process still hasn’t exited after the 30s timeout, DisposeAsync only logs and continues. That can leave a runaway dotnet-watch process (or child processes) running in the Helix work item, causing resource leaks and cross-test interference while also hiding the failure signal. Consider failing the test/cleanup in this case (or at least making the cleanup path retry/force termination and surface the problem), rather than just logging and proceeding to dispose the Process handle.
| catch (OperationCanceledException) | |
| { | |
| Logger.Log($"Process {Id} did not exit within 30 seconds after Kill()"); | |
| catch (OperationCanceledException ex) | |
| { | |
| Logger.Log($"Process {Id} did not exit within 30 seconds after Kill()"); | |
| throw new TimeoutException($"Process {Id} did not exit within 30 seconds after Kill().", ex); |
| try | ||
| { | ||
| Process.StandardInput.Close(); | ||
| } | ||
| catch |
There was a problem hiding this comment.
The broad catch around closing StandardInput/Kill() swallows all exceptions without logging. Since this code was added to address a platform-specific hang, swallowing the exception makes it hard to diagnose when stdin can’t be closed (or why). Consider at least logging the exception details (or narrowing the caught exception types) so cleanup failures are actionable in CI logs.
83d044f to
ae5112f
Compare
a35fa75 to
d9f4358
Compare
d9f4358 to
9949b40
Compare
9949b40 to
ed04d76
Compare
| _isDisposed = true; | ||
|
|
||
| // wait for all in-flight process initialization to complete: | ||
| // If no session initialization is in-flight (_pendingSessionInitializationCount == 0), |
There was a problem hiding this comment.
Not entirely correct either. Ok to merge, I'll follow up with better fix.
There was a problem hiding this comment.
@tmat This is an automatic fix by the AI for flakiness. Don't merge this...when it's gotten 25 passing runs we'll take a second pass over this to smooth it out.
There was a problem hiding this comment.
Yeah, I figured. It looks very AI like. It's great it found the issue. I'll work on a better fix over the weekend.
|
watch changes lgtm |
2eb12cb to
bcae687
Compare
🎯 Milestone: 5 Consecutive PassesValidation Results
Root Causes Fixed
Baseline vs Current
Continuing validation toward 25 consecutive passes target. |
bcae687 to
1644c42
Compare
|
|
||
| REM Set DOTNET_HOST_PATH so MSBuild task hosts can locate the dotnet executable. | ||
| REM Without this, tasks from NuGet packages that use TaskHostFactory fail with MSB4216. | ||
| set DOTNET_HOST_PATH=%DOTNET_ROOT%\dotnet.exe |
| dotnet nuget remove source dotnet-tools-transport --configfile %TestExecutionDirectory%\nuget.config | ||
| dotnet nuget remove source dotnet-libraries --configfile %TestExecutionDirectory%\nuget.config | ||
| dotnet nuget remove source dotnet-eng --configfile %TestExecutionDirectory%\nuget.config | ||
| REM Remove feeds not needed for tests. Errors from non-existent sources |
There was a problem hiding this comment.
any idea which feeds remain? maybe we should have a separate nuget.config just for the tests
|
@akoeplinger This is an automated run...let it go for a while..don't merge or approve. I'll have it go through another pass and also reivew at the end of when it thinks it gets a stable run. |
4cb1ca9 to
ea8fad3
Compare
aa94871 to
fac0ce7
Compare
Root causes fixed: 1. MSB4216 task host failures - DOTNET_HOST_PATH not set in Helix 2. dotnet-watch test hangs - AwaitableProcess timeout cap, disposal race, semaphore deadlock 3. GZipCompress file lock in BlazorWasm parallel compression 4. Missing runtimeconfig.json in test tool NuGet packages 5. CompilationHandler IOException during hot reload file updates 6. BrowserTests.LaunchesBrowserOnStart timing race - sync to async wait 7. NuGet source removal noise in Helix scripts
fac0ce7 to
189c634
Compare
|
#53271 has a better fix for the dotnet-watch race condition. |
Summary
This PR addresses three categories of intermittent CI failures identified through systematic analysis of 30 recent builds (57% failure rate baseline, ~30% after excluding clearly-broken PRs).
Root Cause 1 - MSB4216 task host failures on macOS Helix
Impact: Tests using NuGet package tasks with TaskHostFactory (ComputeWasmBuildAssets from WebAssembly SDK, ComputeManagedAssemblies from ILLink) fail intermittently on macOS Helix machines.
Fix: Export
DOTNET_HOST_PATHin bothRunTestsOnHelix.shandRunTestsOnHelix.cmdso MSBuild can locate the dotnet executable when spawning out-of-process task hosts.Root Cause 2 - dotnet-watch Aspire_BuildError_ManualRestart test hang
Impact: Test hangs for 60+ minutes on Helix, consuming the entire work item timeout.
Fix:
AwaitableProcess.DisposeAsync()Root Cause 3 - Noisy NuGet source removal errors
Impact: Helix test setup produces confusing error messages when trying to remove internal NuGet sources that don't exist in public CI builds.
Fix: Suppress errors from
dotnet nuget remove sourcecommands using|| true(bash) and2>nul(cmd).Validation
Building on the feature branch to establish baseline pass rate. Target: 25+ consecutive passing builds.