Skip to content

Fix flaky AllProjectsHaveSameOutputDirectory on Windows via delete-with-retry#11807

Open
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-incremental-build-test-issue
Open

Fix flaky AllProjectsHaveSameOutputDirectory on Windows via delete-with-retry#11807
Copilot wants to merge 3 commits into
mainfrom
copilot/fix-incremental-build-test-issue

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

IncrementalBuildTest.AllProjectsHaveSameOutputDirectory intermittently fails on Windows with UnauthorizedAccessException : Access to the path 'App1.dll' is denied. SolutionBuilder.Save() creates a fresh ProjectBuilder per project on every call, so BuiltBefore == false and ProjectBuilder.Save() always takes the recursive-delete-and-repopulate branch — deleting a just-built App1.dll whose handle may still be held by VBCSCompiler, Defender, or the search indexer (opened without FILE_SHARE_DELETE).

Changes

  • FileSystemUtils.DeleteDirectoryWithRetry — new helper that sets the directory writable and retries Directory.Delete(..., true) with linear back-off, catching only transient UnauthorizedAccessException/IOException. This neutralizes the race regardless of which process holds the handle, with no change to test semantics.

    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);
            }
        }
    }
  • ProjectBuilder.Save / ProjectBuilder.Cleanup — replaced the inline SetDirectoryWriteable + Directory.Delete with 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.

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
Copilot AI requested a review from simonrozsival June 29, 2026 20:23
@simonrozsival simonrozsival marked this pull request as ready for review June 30, 2026 09:41
Copilot AI review requested due to automatic review settings June 30, 2026 09:41
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 30, 2026

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.

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 transient UnauthorizedAccessException/IOException with backoff.
  • Updated ProjectBuilder.Save() and ProjectBuilder.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);
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot break on DirectoryNotFoundException instead of retrying

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 848ccdf: DeleteDirectoryWithRetry() now treats DirectoryNotFoundException as success (returns immediately) instead of retrying.

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

Labels

flaky-tests ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

3 participants