Add retries to several file system operations
Closes #6815
Rob Mensching committed
Jul 7, 2022 at 17:01 UTC
3dac27d54c104796b85b6582d6f878a553f335fa
3 files changed
+106
-9
src/wix/WixToolset.Core.Native/FileSystem.cs
+38
-6
@@ -7,6 +7,7 @@ namespace WixToolset.Core.Native
7
using System.IO;
8
using System.Runtime.InteropServices;
9
using System.Security.AccessControl;
10
+ using System.Threading;
11
12
/// <summary>
13
/// File system helpers.
@@ -23,13 +24,20 @@ namespace WixToolset.Core.Native
24
{
25
EnsureDirectoryWithoutFile(destination);
26
26
- if (!allowHardlink || !CreateHardLink(destination, source, IntPtr.Zero))
27
+ var hardlinked = false;
28
+
29
+ if (allowHardlink)
30
+ {
31
+ ActionWithRetries(() => hardlinked = CreateHardLink(destination, source, IntPtr.Zero));
32
+ }
33
+
34
+ if (!hardlinked)
35
{
36
#if DEBUG
37
var er = Marshal.GetLastWin32Error();
38
#endif
39
32
- File.Copy(source, destination, overwrite: true);
40
+ ActionWithRetries(() => File.Copy(source, destination, overwrite: true));
41
}
42
}
43
@@ -42,7 +50,7 @@ namespace WixToolset.Core.Native
50
{
51
EnsureDirectoryWithoutFile(destination);
52
45
- File.Move(source, destination);
53
+ ActionWithRetries(() => File.Move(source, destination));
54
}
55
56
/// <summary>
@@ -56,7 +64,31 @@ namespace WixToolset.Core.Native
64
65
foreach (var file in files)
66
{
59
- new FileInfo(file).SetAccessControl(aclReset);
67
+ var fileInfo = new FileInfo(file);
68
+ ActionWithRetries(() => fileInfo.SetAccessControl(aclReset));
69
+ }
70
+ }
71
+
72
+ /// <summary>
73
+ /// Executes an action and retries on any exception up to a few times. Primarily
74
+ /// intended for use with file system operations that might get interrupted by
75
+ /// external systems (usually anti-virus).
76
+ /// </summary>
77
+ /// <param name="action">Action to execute.</param>
78
+ /// <param name="maxRetries">Maximum retry attempts.</param>
79
+ public static void ActionWithRetries(Action action, int maxRetries = 3)
80
+ {
81
+ for (var attempt = 1; attempt <= maxRetries; ++attempt)
82
+ {
83
+ try
84
+ {
85
+ action();
86
+ break;
87
+ }
88
+ catch when (attempt < maxRetries)
89
+ {
90
+ Thread.Sleep(250);
91
+ }
92
}
93
}
94
@@ -66,10 +98,10 @@ namespace WixToolset.Core.Native
98
99
if (!String.IsNullOrEmpty(directory))
100
{
69
- Directory.CreateDirectory(directory);
101
+ ActionWithRetries(() => Directory.CreateDirectory(directory));
102
}
103
72
- File.Delete(path);
104
+ ActionWithRetries(() => File.Delete(path));
105
}
106
107
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+5
-3
@@ -9,6 +9,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
9
using System.Linq;
10
using System.Runtime.InteropServices;
11
using System.Text;
12
+ using System.Threading;
13
+ using WixToolset.Core.Native;
14
using WixToolset.Core.Native.Msi;
15
using WixToolset.Core.Native.Msm;
16
using WixToolset.Data;
@@ -69,10 +71,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
71
{
72
merge = MsmInterop.GetMsmMerge();
73
72
- merge.OpenLog(logPath);
74
+ FileSystem.ActionWithRetries(() => merge.OpenLog(logPath));
75
logOpen = true;
76
75
- merge.OpenDatabase(this.OutputPath);
77
+ FileSystem.ActionWithRetries(() => merge.OpenDatabase(this.OutputPath));
78
databaseOpen = true;
79
80
var featureModulesByMergeId = this.Section.Symbols.OfType<WixFeatureModulesSymbol>().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key);
@@ -97,7 +99,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
99
}
100
101
this.Messaging.Write(VerboseMessages.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
100
- merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
102
+ FileSystem.ActionWithRetries(() => merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage));
103
moduleOpen = true;
104
105
trackedFiles.Add(this.BackendHelper.TrackFile(wixMergeRow.SourceFile, TrackedFileType.Input, wixMergeRow.SourceLineNumbers));
src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs
+63
@@ -3,8 +3,12 @@
3
namespace WixToolsetTest.CoreIntegration
4
{
5
using System.IO;
6
+ using System.Linq;
7
using WixBuildTools.TestSupport;
8
using WixToolset.Core.TestPackage;
9
+ using WixToolset.Data;
10
+ using WixToolset.Data.Symbols;
11
+ using WixToolset.Data.WindowsInstaller;
12
using Xunit;
13
14
public class ModuleFixture
@@ -42,5 +46,64 @@ namespace WixToolsetTest.CoreIntegration
46
}, rows);
47
}
48
}
49
+
50
+ [Fact]
51
+ public void CanMergeModuleAndValidate()
52
+ {
53
+ var msmFolder = TestData.Get(@"TestData\SimpleModule");
54
+ var folder = TestData.Get(@"TestData\SimpleMerge");
55
+
56
+ using (var fs = new DisposableFileSystem())
57
+ {
58
+ var intermediateFolder = fs.GetFolder();
59
+ var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi");
60
+ var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab");
61
+
62
+ var msmResult = WixRunner.Execute(new[]
63
+ {
64
+ "build",
65
+ Path.Combine(msmFolder, "Module.wxs"),
66
+ "-loc", Path.Combine(msmFolder, "Module.en-us.wxl"),
67
+ "-bindpath", Path.Combine(msmFolder, "data"),
68
+ "-intermediateFolder", intermediateFolder,
69
+ "-o", Path.Combine(intermediateFolder, "bin", "test", "test.msm")
70
+ });
71
+
72
+ msmResult.AssertSuccess();
73
+
74
+ var result = WixRunner.Execute(new[]
75
+ {
76
+ "build",
77
+ Path.Combine(folder, "Package.wxs"),
78
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
79
+ "-bindpath", Path.Combine(intermediateFolder, "bin", "test"),
80
+ "-intermediateFolder", intermediateFolder,
81
+ "-o", msiPath
82
+ });
83
+
84
+ result.AssertSuccess();
85
+
86
+ var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
87
+ var section = intermediate.Sections.Single();
88
+ Assert.Empty(section.Symbols.OfType<FileSymbol>());
89
+
90
+ var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
91
+ Assert.Empty(data.Tables["File"].Rows);
92
+
93
+ var results = Query.QueryDatabase(msiPath, new[] { "File" });
94
+ WixAssert.CompareLineByLine(new[]
95
+ {
96
+ "File:File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent1.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tfile1.txt\t17\t\t\t512\t1",
97
+ "File:File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent2.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tfile2.txt\t17\t\t\t512\t2",
98
+ }, results);
99
+
100
+ var files = Query.GetCabinetFiles(cabPath);
101
+ WixAssert.CompareLineByLine(new[]
102
+ {
103
+ "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE",
104
+ "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE"
105
+ }, files.Select(f => f.Name).ToArray());
106
+ }
107
+ }
108
}
109
}