File: FileAccessRetrier.cs
Web Access
Project: ..\..\..\src\Cli\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj (Microsoft.DotNet.Cli.Utils)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Microsoft.DotNet.Cli.Utils;
 
public static class FileAccessRetrier
{
    public static async Task<T> RetryOnFileAccessFailure<T>(
        Func<T> func,
        string errorMessage,
        int maxRetries = 3000,
        TimeSpan sleepDuration = default)
    {
        var attemptsLeft = maxRetries;
 
        if (sleepDuration == default)
        {
            sleepDuration = TimeSpan.FromMilliseconds(10);
        }
 
        while (true)
        {
            if (attemptsLeft < 1)
            {
                throw new InvalidOperationException(errorMessage);
            }
 
            attemptsLeft--;
 
            try
            {
                return func();
            }
            catch (UnauthorizedAccessException)
            {
                // This can occur when the file is being deleted
                // Or when an admin user has locked the file
                await Task.Delay(sleepDuration);
 
                continue;
            }
            catch (IOException)
            {
                await Task.Delay(sleepDuration);
 
                continue;
            }
        }
    }
 
    /// <summary>
    /// Run Directory.Move and File.Move in Windows has a chance to get IOException with
    /// HResult 0x80070005 due to Indexer. But this error is transient.
    /// </summary>
    internal static void RetryOnMoveAccessFailure(Action action)
    {
        const int ERROR_HRESULT_ACCESS_DENIED = unchecked((int)0x80070005);
        int nextWaitTime = 10;
        int remainRetry = 10;
 
        while (true)
        {
            try
            {
                action();
                break;
            }
            catch (IOException e) when (e.HResult == ERROR_HRESULT_ACCESS_DENIED)
            {
                Thread.Sleep(nextWaitTime);
                nextWaitTime *= 2;
                remainRetry--;
                if (remainRetry == 0)
                {
                    throw;
                }
            }
        }
    }
 
    internal static void RetryOnIOException(Action action)
    {
        int nextWaitTime = 10;
        int remainRetry = 10;
 
        while (true)
        {
            try
            {
                action();
                break;
            }
            catch (IOException)
            {
                Task.Run(() => Task.Delay(nextWaitTime)).Wait();
                nextWaitTime *= 2;
                remainRetry--;
                if (remainRetry == 0)
                {
                    throw;
                }
            }
        }
    }
}