diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs
index db607aa6475..0a74a6a2adc 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/ProjectBuilder.cs
@@ -104,10 +104,7 @@ public void Save (XamarinProject project, bool doNotCleanupOnUpdate = false, boo
if (!BuiltBefore) {
if (project.ShouldPopulate) {
- if (Directory.Exists (ProjectDirectory)) {
- FileSystemUtils.SetDirectoryWriteable (ProjectDirectory);
- Directory.Delete (ProjectDirectory, true);
- }
+ FileSystemUtils.DeleteDirectoryWithRetry (ProjectDirectory);
project.Populate (ProjectDirectory, files);
}
@@ -202,10 +199,7 @@ public void Cleanup ()
BuiltBefore = false;
var projectDirectory = Path.Combine (XABuildPaths.TestOutputDirectory, ProjectDirectory);
- if (Directory.Exists (projectDirectory)) {
- FileSystemUtils.SetDirectoryWriteable (projectDirectory);
- Directory.Delete (projectDirectory, true);
- }
+ FileSystemUtils.DeleteDirectoryWithRetry (projectDirectory);
}
public struct RuntimeInfo
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs
index e66a1568c67..aeec60a3440 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/SolutionBuilder.cs
@@ -99,7 +99,7 @@ protected override void Dispose (bool disposing)
if (disposing)
if (BuildSucceeded && !string.IsNullOrEmpty (SolutionPath))
try {
- Directory.Delete (SolutionPath, recursive: true);
+ FileSystemUtils.DeleteDirectoryWithRetry (SolutionPath);
} catch (Exception) {
// This happens on CI occasionally, let's not fail the test
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/FileSystemUtils.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/FileSystemUtils.cs
index 55fb9c0d199..b49eec03c4c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/FileSystemUtils.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/FileSystemUtils.cs
@@ -40,6 +40,37 @@ public static void SetDirectoryWriteable (string directory)
}
}
+ ///
+ /// Recursively deletes a directory, retrying on transient failures.
+ ///
+ /// The directory path to delete.
+ /// The maximum number of retries before giving up.
+ ///
+ /// On Windows, a handle to a just-written file can still be held by another process
+ /// (e.g. the Roslyn shared-compilation server, an anti-virus scanner, or the search
+ /// indexer), causing to throw
+ /// or . This method
+ /// backs off and retries to let the other process release the handle.
+ ///
+ ///
+ public static void DeleteDirectoryWithRetry (string directory, int retries = 10)
+ {
+ if (!Directory.Exists (directory))
+ return;
+
+ for (int i = 0; ; i++) {
+ try {
+ SetDirectoryWriteable (directory);
+ Directory.Delete (directory, true);
+ return;
+ } catch (DirectoryNotFoundException) {
+ 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
+ }
+ }
+ }
+
///
/// Sets a single file to be writable by removing the read-only attribute if present.
///