diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools index ab2165daf27..60fae1924e2 160000 --- a/external/xamarin-android-tools +++ b/external/xamarin-android-tools @@ -1 +1 @@ -Subproject commit ab2165daf27d4fcb29e88bc022e0ab0be33aff69 +Subproject commit 60fae1924e2d71f31dc1ed05f7346fd7874d6636 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs index c456b87eefd..da33725f0e6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs @@ -28,6 +28,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Runtime.InteropServices; using Microsoft.Build.Framework; using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; @@ -38,6 +40,9 @@ public class RemoveDirFixed : AndroidTask { public override string TaskPrefix => "RDF"; + const int ERROR_ACCESS_DENIED = -2147024891; + const int ERROR_SHARING_VIOLATION = -2147024864; + public override bool RunTask () { var temporaryRemovedDirectories = new List (Directories.Length); @@ -48,36 +53,41 @@ public override bool RunTask () Log.LogDebugMessage ($"Directory did not exist: {fullPath}"); continue; } + int retryCount = 0; + int attempts = Files.GetFileWriteRetryAttempts (); + int delay = Files.GetFileWriteRetryDelay (); try { - // try to do a normal "fast" delete of the directory. - Directory.Delete (fullPath, true); - temporaryRemovedDirectories.Add (directory); - } catch (UnauthorizedAccessException ex) { - // if that fails we probably have readonly files (or locked files) - // so try to make them writable and try again. - try { - Files.SetDirectoryWriteable (fullPath); - Directory.Delete (fullPath, true); - temporaryRemovedDirectories.Add (directory); - } catch (Exception inner) { - Log.LogUnhandledException (TaskPrefix, ex); - Log.LogUnhandledException (TaskPrefix, inner); - } - } catch (DirectoryNotFoundException ex) { - // This could be a file inside the directory over MAX_PATH. - // We can attempt using the \\?\ syntax. - if (OS.IsWindows) { + while (retryCount <= attempts) { try { - fullPath = Files.ToLongPath (fullPath); - Log.LogDebugMessage ("Trying long path: " + fullPath); + // try to do a normal "fast" delete of the directory. + // only do the set writable on the second attempt + if (retryCount == 1) + Files.SetDirectoryWriteable (fullPath); Directory.Delete (fullPath, true); temporaryRemovedDirectories.Add (directory); - } catch (Exception inner) { - Log.LogUnhandledException (TaskPrefix, ex); - Log.LogUnhandledException (TaskPrefix, inner); + break; + } catch (Exception e) { + switch (e) { + case DirectoryNotFoundException: + if (OS.IsWindows) { + fullPath = Files.ToLongPath (fullPath); + Log.LogDebugMessage ("Trying long path: " + fullPath); + break; + } + throw; + case UnauthorizedAccessException: + case IOException: + int code = Marshal.GetHRForException(e); + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || retryCount >= attempts) { + throw; + }; + break; + default: + throw; + } + Thread.Sleep (delay); + retryCount++; } - } else { - Log.LogUnhandledException (TaskPrefix, ex); } } catch (Exception ex) { Log.LogUnhandledException (TaskPrefix, ex); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/RemoveDirTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/RemoveDirTests.cs index bf93d07faca..20a828e8f4c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/RemoveDirTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/RemoveDirTests.cs @@ -7,6 +7,8 @@ using Xamarin.Android.Tasks; using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; +using TPL = System.Threading.Tasks; +using System.Threading; namespace Xamarin.Android.Build.Tests { @@ -83,5 +85,44 @@ public void LongPath () Assert.AreEqual (1, task.RemovedDirectories.Length, "Changes should have been made."); DirectoryAssert.DoesNotExist (tempDirectory); } + + [Test, Category ("SmokeTests")] + public void DirectoryInUse () + { + if (OS.IsMac) { + Assert.Ignore ("This is not an issue on macos."); + return; + } + var file = NewFile (); + var task = CreateTask (); + using (var f = File.OpenWrite (file)) { + Assert.IsFalse (task.Execute (), "task.Execute() should have failed."); + Assert.AreEqual (0, task.RemovedDirectories.Length, "Changes should not have been made."); + DirectoryAssert.Exists (tempDirectory); + } + } + + [Test, Category ("SmokeTests")] + public async TPL.Task DirectoryInUseWithRetry () + { + if (OS.IsMac) { + Assert.Ignore ("This is not an issue on macos."); + return; + } + var file = NewFile (); + var task = CreateTask (); + var ev = new ManualResetEvent (false); + var t = TPL.Task.Run (async () => { + using (var f = File.OpenWrite (file)) { + ev.Set (); + await TPL.Task.Delay (2500); + } + }); + ev.WaitOne (); + Assert.IsTrue (task.Execute (), "task.Execute() should have succeeded."); + Assert.AreEqual (1, task.RemovedDirectories.Length, "Changes should have been made."); + DirectoryAssert.DoesNotExist (tempDirectory); + await t; + } } }