Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,37 @@ public static void SetDirectoryWriteable (string directory)
}
}

/// <summary>
/// Recursively deletes a directory, retrying on transient failures.
/// </summary>
/// <param name="directory">The directory path to delete.</param>
/// <param name="retries">The maximum number of retries before giving up.</param>
/// <remarks>
/// 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 <see cref="Directory.Delete(string, bool)"/> to throw
/// <see cref="UnauthorizedAccessException"/> or <see cref="IOException"/>. This method
/// backs off and retries to let the other process release the handle.
/// </remarks>
/// <seealso cref="SetDirectoryWriteable(string)"/>
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
}
}
}

/// <summary>
/// Sets a single file to be writable by removing the read-only attribute if present.
/// </summary>
Expand Down
Loading