From ea539c7f6345303f4309c3f6aa0c5a3012839cad Mon Sep 17 00:00:00 2001 From: William Li Date: Tue, 22 May 2018 09:55:10 -0700 Subject: [PATCH] Add retry when Directory.Move (#9313) --- .../FileAccessRetryer.cs | 31 +++++++++++++++++++ src/dotnet/ShellShim/ShellShimRepository.cs | 4 +-- .../ToolPackage/ToolPackageInstaller.cs | 3 +- src/dotnet/ToolPackage/ToolPackageInstance.cs | 5 +-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.DotNet.Cli.Utils/FileAccessRetryer.cs b/src/Microsoft.DotNet.Cli.Utils/FileAccessRetryer.cs index f73d5d4d1..80201ecac 100644 --- a/src/Microsoft.DotNet.Cli.Utils/FileAccessRetryer.cs +++ b/src/Microsoft.DotNet.Cli.Utils/FileAccessRetryer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.DotNet.Cli.Utils @@ -47,5 +48,35 @@ namespace Microsoft.DotNet.Cli.Utils } } } + + /// + /// 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. + /// + 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; + } + } + } + } } } diff --git a/src/dotnet/ShellShim/ShellShimRepository.cs b/src/dotnet/ShellShim/ShellShimRepository.cs index 121582cf6..a2ebd78a4 100644 --- a/src/dotnet/ShellShim/ShellShimRepository.cs +++ b/src/dotnet/ShellShim/ShellShimRepository.cs @@ -113,7 +113,7 @@ namespace Microsoft.DotNet.ShellShim foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value))) { var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - _fileSystem.File.Move(file.Value, tempPath); + FileAccessRetrier.RetryOnMoveAccessFailure(() => _fileSystem.File.Move(file.Value, tempPath)); files[file.Value] = tempPath; } } @@ -137,7 +137,7 @@ namespace Microsoft.DotNet.ShellShim rollback: () => { foreach (var kvp in files) { - _fileSystem.File.Move(kvp.Value, kvp.Key); + FileAccessRetrier.RetryOnMoveAccessFailure(() => _fileSystem.File.Move(kvp.Value, kvp.Key)); } }); } diff --git a/src/dotnet/ToolPackage/ToolPackageInstaller.cs b/src/dotnet/ToolPackage/ToolPackageInstaller.cs index 0fce39a61..14ea51d25 100644 --- a/src/dotnet/ToolPackage/ToolPackageInstaller.cs +++ b/src/dotnet/ToolPackage/ToolPackageInstaller.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; @@ -82,7 +83,7 @@ namespace Microsoft.DotNet.ToolPackage } Directory.CreateDirectory(packageRootDirectory.Value); - Directory.Move(stageDirectory.Value, packageDirectory.Value); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(stageDirectory.Value, packageDirectory.Value)); rollbackDirectory = packageDirectory.Value; return new ToolPackageInstance(_store, packageId, version, packageDirectory); diff --git a/src/dotnet/ToolPackage/ToolPackageInstance.cs b/src/dotnet/ToolPackage/ToolPackageInstance.cs index c8be8d5ca..9a8041582 100644 --- a/src/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/dotnet/ToolPackage/ToolPackageInstance.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.ProjectModel; using NuGet.Versioning; using Microsoft.DotNet.Cli.Utils; +using System.Threading; namespace Microsoft.DotNet.ToolPackage { @@ -79,7 +80,7 @@ namespace Microsoft.DotNet.ToolPackage // Use the staging directory for uninstall // This prevents cross-device moves when temp is mounted to a different device var tempPath = _store.GetRandomStagingDirectory().Value; - Directory.Move(PackageDirectory.Value, tempPath); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(PackageDirectory.Value, tempPath)); tempPackageDirectory = tempPath; } @@ -111,7 +112,7 @@ namespace Microsoft.DotNet.ToolPackage if (tempPackageDirectory != null) { Directory.CreateDirectory(rootDirectory.Value); - Directory.Move(tempPackageDirectory, PackageDirectory.Value); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(tempPackageDirectory, PackageDirectory.Value)); } }); }