From ca4bdd3ff00a1a46979cb73885456affd6a0a563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto?= Date: Thu, 23 Apr 2026 11:35:07 +0200 Subject: [PATCH] Fixes #10124: retry RemoveDirFixed on ERROR_DIR_NOT_EMPTY The retry loop in RemoveDirFixed only kicks in for ERROR_ACCESS_DENIED (0x80070005) and ERROR_SHARING_VIOLATION (0x80070020). The transient "Directory not empty" failure observed on Linux during `dotnet publish` for Android raises an IOException with HResult 0x80070091 (ERROR_DIR_NOT_EMPTY), which falls through the whitelist and aborts the task on the first attempt. On Unix, .NET maps `ENOTEMPTY` from `rmdir(2)` to ERROR_DIR_NOT_EMPTY, so the same constant covers both Windows and Unix sources. Adding it to the whitelist lets the existing retry-with-backoff machinery do its job, with no behavioral change after `RetryAttempts` is exhausted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/RemoveDirFixed.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs index bcc8ff12ed5..b0b43e2b2c4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs @@ -42,8 +42,15 @@ public class RemoveDirFixed : AndroidTask { public override string TaskPrefix => "RDF"; - const int ERROR_ACCESS_DENIED = -2147024891; - const int ERROR_SHARING_VIOLATION = -2147024864; + const int ERROR_ACCESS_DENIED = -2147024891; // 0x80070005 + const int ERROR_SHARING_VIOLATION = -2147024864; // 0x80070020 + // On Unix, .NET maps `ENOTEMPTY` from `rmdir(2)` to this Win32 HResult, + // so the same constant covers both Windows and Unix sources of + // "Directory not empty". This is observed on NTFS volumes mounted via + // the Linux `ntfs3` driver, where directory metadata can momentarily + // report children that have just been unlinked, but is not specific + // to that filesystem. + const int ERROR_DIR_NOT_EMPTY = -2147024751; // 0x80070091 public override bool RunTask () { @@ -80,7 +87,7 @@ public override bool RunTask () case UnauthorizedAccessException: case IOException: int code = Marshal.GetHRForException(e); - if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || retryCount >= attempts) { + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION && code != ERROR_DIR_NOT_EMPTY) || retryCount >= attempts) { throw; }; break;