Fix flaky AllProjectsHaveSameOutputDirectory on Windows via delete-with-retry#11807
Open
Copilot wants to merge 3 commits into
Open
Fix flaky AllProjectsHaveSameOutputDirectory on Windows via delete-with-retry#11807Copilot wants to merge 3 commits into
Copilot wants to merge 3 commits into
Conversation
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix flaky test for IncrementalBuildTest on Windows
Fix flaky AllProjectsHaveSameOutputDirectory on Windows via delete-with-retry
Jun 29, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses intermittent Windows test failures in IncrementalBuildTest.AllProjectsHaveSameOutputDirectory by making test-directory cleanup more resilient to transient file locks (e.g., Roslyn shared compilation server, AV, or indexer) via a delete-with-retry helper in Xamarin.ProjectTools.
Changes:
- Added
FileSystemUtils.DeleteDirectoryWithRetry()to retry recursive directory deletion on transientUnauthorizedAccessException/IOExceptionwith backoff. - Updated
ProjectBuilder.Save()andProjectBuilder.Cleanup()to use the new helper instead of inline delete logic. - Updated
SolutionBuilder.Dispose()to route recursive solution directory cleanup through the helper (while retaining existing swallow-on-CI behavior).
Show a summary per file
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/FileSystemUtils.cs | Introduces DeleteDirectoryWithRetry for transient-lock tolerant recursive deletion. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs | Uses the new retrying delete helper during solution cleanup in Dispose. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs | Replaces direct recursive deletes with the retrying helper in Save and Cleanup. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 1
Comment on lines
+56
to
+71
| public static void DeleteDirectoryWithRetry (string directory, int retries = 10) | ||
| { | ||
| if (!Directory.Exists (directory)) | ||
| return; | ||
|
|
||
| SetDirectoryWriteable (directory); | ||
| for (int i = 0; ; i++) { | ||
| try { | ||
| Directory.Delete (directory, true); | ||
| return; | ||
| } catch (Exception e) when ((e is UnauthorizedAccessException || e is IOException) && i < retries) { | ||
| Thread.Sleep (200 * (i + 1)); // back off; let AV/Roslyn release the handle | ||
| SetDirectoryWriteable (directory); | ||
| } | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
@copilot break on DirectoryNotFoundException instead of retrying
Contributor
Author
There was a problem hiding this comment.
Updated in 848ccdf: DeleteDirectoryWithRetry() now treats DirectoryNotFoundException as success (returns immediately) instead of retrying.
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IncrementalBuildTest.AllProjectsHaveSameOutputDirectoryintermittently fails on Windows withUnauthorizedAccessException : Access to the path 'App1.dll' is denied.SolutionBuilder.Save()creates a freshProjectBuilderper project on every call, soBuiltBefore == falseandProjectBuilder.Save()always takes the recursive-delete-and-repopulate branch — deleting a just-builtApp1.dllwhose handle may still be held by VBCSCompiler, Defender, or the search indexer (opened withoutFILE_SHARE_DELETE).Changes
FileSystemUtils.DeleteDirectoryWithRetry— new helper that sets the directory writable and retriesDirectory.Delete(..., true)with linear back-off, catching only transientUnauthorizedAccessException/IOException. This neutralizes the race regardless of which process holds the handle, with no change to test semantics.ProjectBuilder.Save/ProjectBuilder.Cleanup— replaced the inlineSetDirectoryWriteable+Directory.Deletewith the new helper.SolutionBuilder.Dispose— same recursive delete now routed through the helper (existing swallow-on-CI behavior retained).The loop's final iteration (
i >= retries) lets the original exception propagate, preserving fail-loud behavior if the lock is genuinely permanent.