From 3dac27d54c104796b85b6582d6f878a553f335fa Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Jul 2022 17:01:55 -0700 Subject: Add retries to several file system operations Closes #6815 --- src/wix/WixToolset.Core.Native/FileSystem.cs | 44 ++++++++++++--- .../Bind/MergeModulesCommand.cs | 8 +-- .../ModuleFixture.cs | 63 ++++++++++++++++++++++ 3 files changed, 106 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/wix/WixToolset.Core.Native/FileSystem.cs b/src/wix/WixToolset.Core.Native/FileSystem.cs index 3dc336d6..3c59c90c 100644 --- a/src/wix/WixToolset.Core.Native/FileSystem.cs +++ b/src/wix/WixToolset.Core.Native/FileSystem.cs @@ -7,6 +7,7 @@ namespace WixToolset.Core.Native using System.IO; using System.Runtime.InteropServices; using System.Security.AccessControl; + using System.Threading; /// /// File system helpers. @@ -23,13 +24,20 @@ namespace WixToolset.Core.Native { EnsureDirectoryWithoutFile(destination); - if (!allowHardlink || !CreateHardLink(destination, source, IntPtr.Zero)) + var hardlinked = false; + + if (allowHardlink) + { + ActionWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero)); + } + + if (!hardlinked) { #if DEBUG var er = Marshal.GetLastWin32Error(); #endif - File.Copy(source, destination, overwrite: true); + ActionWithRetries(() => File.Copy(source, destination, overwrite: true)); } } @@ -42,7 +50,7 @@ namespace WixToolset.Core.Native { EnsureDirectoryWithoutFile(destination); - File.Move(source, destination); + ActionWithRetries(() => File.Move(source, destination)); } /// @@ -56,7 +64,31 @@ namespace WixToolset.Core.Native foreach (var file in files) { - new FileInfo(file).SetAccessControl(aclReset); + var fileInfo = new FileInfo(file); + ActionWithRetries(() => fileInfo.SetAccessControl(aclReset)); + } + } + + /// + /// Executes an action and retries on any exception up to a few times. Primarily + /// intended for use with file system operations that might get interrupted by + /// external systems (usually anti-virus). + /// + /// Action to execute. + /// Maximum retry attempts. + public static void ActionWithRetries(Action action, int maxRetries = 3) + { + for (var attempt = 1; attempt <= maxRetries; ++attempt) + { + try + { + action(); + break; + } + catch when (attempt < maxRetries) + { + Thread.Sleep(250); + } } } @@ -66,10 +98,10 @@ namespace WixToolset.Core.Native if (!String.IsNullOrEmpty(directory)) { - Directory.CreateDirectory(directory); + ActionWithRetries(() => Directory.CreateDirectory(directory)); } - File.Delete(path); + ActionWithRetries(() => File.Delete(path)); } [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index 96b7866c..7af0ca19 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -9,6 +9,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Linq; using System.Runtime.InteropServices; using System.Text; + using System.Threading; + using WixToolset.Core.Native; using WixToolset.Core.Native.Msi; using WixToolset.Core.Native.Msm; using WixToolset.Data; @@ -69,10 +71,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind { merge = MsmInterop.GetMsmMerge(); - merge.OpenLog(logPath); + FileSystem.ActionWithRetries(() => merge.OpenLog(logPath)); logOpen = true; - merge.OpenDatabase(this.OutputPath); + FileSystem.ActionWithRetries(() => merge.OpenDatabase(this.OutputPath)); databaseOpen = true; var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); @@ -97,7 +99,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } this.Messaging.Write(VerboseMessages.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); - merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); + FileSystem.ActionWithRetries(() => merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage)); moduleOpen = true; trackedFiles.Add(this.BackendHelper.TrackFile(wixMergeRow.SourceFile, TrackedFileType.Input, wixMergeRow.SourceLineNumbers)); diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs index af200c0d..89ad7bc7 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs @@ -3,8 +3,12 @@ namespace WixToolsetTest.CoreIntegration { using System.IO; + using System.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; using Xunit; public class ModuleFixture @@ -42,5 +46,64 @@ namespace WixToolsetTest.CoreIntegration }, rows); } } + + [Fact] + public void CanMergeModuleAndValidate() + { + var msmFolder = TestData.Get(@"TestData\SimpleModule"); + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); + + var msmResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(msmFolder, "Module.wxs"), + "-loc", Path.Combine(msmFolder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(msmFolder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, "bin", "test", "test.msm") + }); + + msmResult.AssertSuccess(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(intermediateFolder, "bin", "test"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + Assert.Empty(section.Symbols.OfType()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + Assert.Empty(data.Tables["File"].Rows); + + var results = Query.QueryDatabase(msiPath, new[] { "File" }); + WixAssert.CompareLineByLine(new[] + { + "File:File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent1.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tfile1.txt\t17\t\t\t512\t1", + "File:File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent2.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tfile2.txt\t17\t\t\t512\t2", + }, results); + + var files = Query.GetCabinetFiles(cabPath); + WixAssert.CompareLineByLine(new[] + { + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, files.Select(f => f.Name).ToArray()); + } + } } } -- cgit v1.2.3-55-g6feb