Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

Retry MoveWithRollback on transient access errors from AV file scanning#366

Closed
rmarinho with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-movewithrollback-access-denied
Closed

Retry MoveWithRollback on transient access errors from AV file scanning#366
rmarinho with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-movewithrollback-access-denied

Conversation

Copilot AI commented May 20, 2026

Copy link
Copy Markdown
Contributor

On Windows, FileUtil.MoveWithRollback intermittently fails with Access to the path '...' is denied because Defender's minifilter still holds read handles on files freshly written by ExtractZipSafe when Directory.Move runs immediately after. MoveFileExW requires exclusive access and surfaces this as UnauthorizedAccessException or IOException (sharing violation).

Changes (src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs)

  • Retry with exponential backoff around the Directory.Move(sourcePath, targetPath) call inside MoveWithRollback. Up to 3 retries (4 attempts total) with delays 500ms → 1000ms → 2000ms.
  • Retryable predicate matches only transient sharing-violation-style failures:
    • UnauthorizedAccessException
    • IOException whose HResult is ERROR_SHARING_VIOLATION (0x80070020) or ERROR_LOCK_VIOLATION (0x80070021). HResult is preferred over message matching since exception messages vary by locale and runtime.
    • Message substring "being used by another process" as a fallback for runtimes that don't populate HResult.
  • Non-retryable exceptions (e.g. DirectoryNotFoundException) propagate immediately, so the existing backup/rollback path is unaffected.
static void MoveDirectoryWithRetry (string sourcePath, string targetPath, Action<TraceLevel, string> logger)
{
    const int maxRetries = 3;
    int delayMs = 500;
    for (int attempt = 0; ; attempt++) {
        try {
            Directory.Move (sourcePath, targetPath);
            return;
        }
        catch (Exception ex) when (attempt < maxRetries && IsRetryableMoveError (ex)) {
            logger (TraceLevel.Warning,
                $"Directory.Move failed (attempt {attempt + 1}/{maxRetries + 1}), retrying in {delayMs}ms: {ex.Message}");
            Thread.Sleep (delayMs);
            delayMs *= 2;
        }
    }
}

The retry is scoped to the move itself, so the caller does not need to re-download/re-extract the archive on a transient Defender race.

Copilot AI changed the title [WIP] Fix MoveWithRollback intermittent access denied issue on Windows Retry MoveWithRollback on transient access errors from AV file scanning May 20, 2026
Copilot AI requested a review from rmarinho May 20, 2026 13:18
@jonathanpeppers

Copy link
Copy Markdown
Member

This repo was moved to dotnet/android and this one will be archived shortly:

Recreate this PR there if it is still needed, thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MoveWithRollback fails intermittently with 'Access denied' on Windows due to Defender file scanning

3 participants