Merge pull request #305 from Noggog/patch-freeze-investigation

Patch freeze investigation
This commit is contained in:
Timothy Baldridge 2019-12-22 21:17:00 -08:00 committed by GitHub
commit b1b4ac5829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 259 additions and 33 deletions

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -208,6 +208,7 @@ namespace Wabbajack.Common
if (p.ExitCode == 0) if (p.ExitCode == 0)
{ {
Utils.Status($"Extracting {name} - 100%", 100, alsoLog: true);
return; return;
} }
Utils.Log(new _7zipReturnError(p.ExitCode, source, dest, p.StandardOutput.ReadToEnd())); Utils.Log(new _7zipReturnError(p.ExitCode, source, dest, p.StandardOutput.ReadToEnd()));

View File

@ -597,12 +597,15 @@ namespace Wabbajack.Common
// To avoid thread starvation, we'll start to help out in the work queue // To avoid thread starvation, we'll start to help out in the work queue
if (WorkQueue.WorkerThread) if (WorkQueue.WorkerThread)
{
while (remainingTasks > 0) while (remainingTasks > 0)
{
if (queue.Queue.TryTake(out var a, 500)) if (queue.Queue.TryTake(out var a, 500))
{ {
WorkQueue.AsyncLocalCurrentQueue.Value = WorkQueue.ThreadLocalCurrentQueue.Value;
await a(); await a();
} }
}
}
return await Task.WhenAll(tasks); return await Task.WhenAll(tasks);
} }
@ -634,12 +637,15 @@ namespace Wabbajack.Common
// To avoid thread starvation, we'll start to help out in the work queue // To avoid thread starvation, we'll start to help out in the work queue
if (WorkQueue.WorkerThread) if (WorkQueue.WorkerThread)
{
while (remainingTasks > 0) while (remainingTasks > 0)
{
if (queue.Queue.TryTake(out var a, 500)) if (queue.Queue.TryTake(out var a, 500))
{ {
WorkQueue.AsyncLocalCurrentQueue.Value = WorkQueue.ThreadLocalCurrentQueue.Value;
await a(); await a();
} }
}
}
return await Task.WhenAll(tasks); return await Task.WhenAll(tasks);
} }
@ -672,12 +678,15 @@ namespace Wabbajack.Common
// To avoid thread starvation, we'll start to help out in the work queue // To avoid thread starvation, we'll start to help out in the work queue
if (WorkQueue.WorkerThread) if (WorkQueue.WorkerThread)
{
while (remainingTasks > 0) while (remainingTasks > 0)
{
if (queue.Queue.TryTake(out var a, 500)) if (queue.Queue.TryTake(out var a, 500))
{ {
WorkQueue.AsyncLocalCurrentQueue.Value = WorkQueue.ThreadLocalCurrentQueue.Value;
await a(); await a();
} }
}
}
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }

View File

@ -20,8 +20,8 @@ namespace Wabbajack.Common
private static readonly AsyncLocal<int> _cpuId = new AsyncLocal<int>(); private static readonly AsyncLocal<int> _cpuId = new AsyncLocal<int>();
public int CpuId => _cpuId.Value; public int CpuId => _cpuId.Value;
internal static bool WorkerThread => ThreadLocalCurrentQueue.Value != null; public static bool WorkerThread => AsyncLocalCurrentQueue.Value != null;
internal static readonly ThreadLocal<WorkQueue> ThreadLocalCurrentQueue = new ThreadLocal<WorkQueue>(); public bool IsWorkerThread => WorkerThread;
internal static readonly AsyncLocal<WorkQueue> AsyncLocalCurrentQueue = new AsyncLocal<WorkQueue>(); internal static readonly AsyncLocal<WorkQueue> AsyncLocalCurrentQueue = new AsyncLocal<WorkQueue>();
private readonly Subject<CPUStatus> _Status = new Subject<CPUStatus>(); private readonly Subject<CPUStatus> _Status = new Subject<CPUStatus>();
@ -61,7 +61,6 @@ namespace Wabbajack.Common
private async Task ThreadBody(int idx) private async Task ThreadBody(int idx)
{ {
_cpuId.Value = idx; _cpuId.Value = idx;
ThreadLocalCurrentQueue.Value = this;
AsyncLocalCurrentQueue.Value = this; AsyncLocalCurrentQueue.Value = this;
try try

View File

@ -221,13 +221,15 @@ namespace Wabbajack.Lib
public async Task<Archive> ResolveArchive(IndexedArchive archive) public async Task<Archive> ResolveArchive(IndexedArchive archive)
{ {
Utils.Status($"Checking link for {archive.Name}", alsoLog: true);
if (archive.IniData == null) if (archive.IniData == null)
Error( Error(
$"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again."); $"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again.");
var result = new Archive var result = new Archive
{ {
State = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(archive.IniData) State = await DownloadDispatcher.ResolveArchive(archive.IniData)
}; };
if (result.State == null) if (result.State == null)
@ -238,8 +240,6 @@ namespace Wabbajack.Lib
result.Meta = archive.Meta; result.Meta = archive.Meta;
result.Size = archive.File.Size; result.Size = archive.File.Size;
Info($"Checking link for {archive.Name}");
if (result.State != null && !await result.State.Verify()) if (result.State != null && !await result.State.Verify())
Error( Error(
$"Unable to resolve link for {archive.Name}. If this is hosted on the Nexus the file may have been removed."); $"Unable to resolve link for {archive.Name}. If this is hosted on the Nexus the file may have been removed.");

View File

@ -65,10 +65,7 @@ namespace Wabbajack.Lib.CompilationSteps
var id = Guid.NewGuid().ToString(); var id = Guid.NewGuid().ToString();
var matches = await sourceFiles.PMap(_mo2Compiler.Queue, e => _mo2Compiler.RunStack(stack, new RawSourceFile(e) var matches = await sourceFiles.PMap(_mo2Compiler.Queue, e => _mo2Compiler.RunStack(stack, new RawSourceFile(e, Path.Combine(Consts.BSACreationDir, id, e.Name))));
{
Path = Path.Combine(Consts.BSACreationDir, id, e.Name)
}));
foreach (var match in matches) foreach (var match in matches)

View File

@ -11,11 +11,14 @@ namespace Wabbajack.Lib
{ {
public class RawSourceFile public class RawSourceFile
{ {
// ToDo
// Make readonly
public string Path; public string Path;
public RawSourceFile(VirtualFile file) public RawSourceFile(VirtualFile file, string path)
{ {
File = file; File = file;
Path = path;
} }
public string AbsolutePath => File.StagedPath; public string AbsolutePath => File.StagedPath;

View File

@ -119,8 +119,7 @@ namespace Wabbajack.Lib
{ {
lootFiles = Directory.EnumerateFiles(lootPath, "userlist.yaml", SearchOption.AllDirectories) lootFiles = Directory.EnumerateFiles(lootPath, "userlist.yaml", SearchOption.AllDirectories)
.Where(p => p.FileExists()) .Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], Path.Combine(Consts.LOOTFolderFilesDir, p.RelativeTo(lootPath))));
{ Path = Path.Combine(Consts.LOOTFolderFilesDir, p.RelativeTo(lootPath)) });
} }
if (cancel.IsCancellationRequested) return false; if (cancel.IsCancellationRequested) return false;
@ -158,12 +157,11 @@ namespace Wabbajack.Lib
var mo2Files = Directory.EnumerateFiles(MO2Folder, "*", SearchOption.AllDirectories) var mo2Files = Directory.EnumerateFiles(MO2Folder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists()) .Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) { Path = p.RelativeTo(MO2Folder) }); .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], p.RelativeTo(MO2Folder)));
var gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories) var gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists()) .Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))));
{ Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath)) });
ModMetas = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods")) ModMetas = Directory.EnumerateDirectories(Path.Combine(MO2Folder, "mods"))
@ -229,12 +227,12 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep($"Adding {ExtraFiles.Count} that were generated by the stack"); UpdateTracker.NextStep($"Adding {ExtraFiles.Count} that were generated by the stack");
results = results.Concat(ExtraFiles).ToArray(); results = results.Concat(ExtraFiles).ToArray();
var nomatch = results.OfType<NoMatch>(); var nomatch = results.OfType<NoMatch>().ToArray();
Info($"No match for {nomatch.Count()} files"); Info($"No match for {nomatch.Length} files");
foreach (var file in nomatch)
Info($" {file.To}");
if (nomatch.Any()) if (nomatch.Any())
{ {
foreach (var file in nomatch)
Info($" {file.To}");
if (IgnoreMissingFiles) if (IgnoreMissingFiles)
{ {
Info("Continuing even though files were missing at the request of the user."); Info("Continuing even though files were missing at the request of the user.");

View File

@ -117,18 +117,15 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Finding Install Files"); UpdateTracker.NextStep("Finding Install Files");
var vortexStagingFiles = Directory.EnumerateFiles(StagingFolder, "*", SearchOption.AllDirectories) var vortexStagingFiles = Directory.EnumerateFiles(StagingFolder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists() && p != StagingMarkerName && !p.Contains(Consts.ManualGameFilesDir)) .Where(p => p.FileExists() && p != StagingMarkerName && !p.Contains(Consts.ManualGameFilesDir))
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], p.RelativeTo(StagingFolder)));
{Path = p.RelativeTo(StagingFolder)});
var vortexDownloads = Directory.EnumerateFiles(DownloadsFolder, "*", SearchOption.AllDirectories) var vortexDownloads = Directory.EnumerateFiles(DownloadsFolder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists() && p != DownloadMarkerName) .Where(p => p.FileExists() && p != DownloadMarkerName)
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], p.RelativeTo(DownloadsFolder)));
{Path = p.RelativeTo(DownloadsFolder)});
var gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories) var gameFiles = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists()) .Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p]) .Select(p => new RawSourceFile(VFS.Index.ByRootPath[p], Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))));
{ Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath)) });
Info("Indexing Archives"); Info("Indexing Archives");
IndexedArchives = Directory.EnumerateFiles(DownloadsFolder) IndexedArchives = Directory.EnumerateFiles(DownloadsFolder)

View File

@ -80,7 +80,7 @@ namespace Wabbajack.Test
var downloadsFolder = Path.Combine(tempDir.Dir.FullName, "downloads"); var downloadsFolder = Path.Combine(tempDir.Dir.FullName, "downloads");
Directory.CreateDirectory(downloadsFolder); Directory.CreateDirectory(downloadsFolder);
File.Create(Path.Combine(tempDir.Dir.FullName, $"downloads/someFile.txt")); File.Create(Path.Combine(tempDir.Dir.FullName, $"downloads/someFile.txt"));
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: downloadsFolder).Succeeded); Assert.IsTrue(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: downloadsFolder).Succeeded);
} }
} }
#endregion #endregion

220
Wabbajack.Test/PMapTests.cs Normal file
View File

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
namespace Wabbajack.Test
{
[TestClass]
public class PMapTests
{
const int TypicalThreadCount = 2;
const int TypicalDelayMS = 50;
[TestMethod]
public async Task Typical_Action()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var output = new List<int>();
await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
Thread.Sleep(TypicalDelayMS);
lock (output)
{
output.Add(item);
}
});
Assert.IsTrue(input.SequenceEqual(output.OrderBy(i => i)));
}
}
[TestMethod]
public async Task Typical_Func()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var results = await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
Thread.Sleep(TypicalDelayMS);
return item.ToString();
});
Assert.IsTrue(input.Select(i => i.ToString()).SequenceEqual(results));
}
}
[TestMethod]
public async Task Typical_Task()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var output = new List<int>();
await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Task.Delay(TypicalDelayMS);
lock (output)
{
output.Add(item);
}
});
Assert.IsTrue(input.SequenceEqual(output.OrderBy(i => i)));
}
}
[TestMethod]
public async Task Typical_TaskReturn()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var results = await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Task.Delay(TypicalDelayMS);
return item.ToString();
});
Assert.IsTrue(input.Select(i => i.ToString()).SequenceEqual(results));
}
}
[TestMethod]
public async Task NestedAction()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var inputConstructedResults = input.SelectMany(i => Enumerable.Range(i * 100, TypicalThreadCount * 2));
var output = new List<int>();
await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Enumerable.Range(item * 100, TypicalThreadCount * 2)
.PMap(queue, async (subItem) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
Thread.Sleep(TypicalDelayMS);
lock (output)
{
output.Add(subItem);
}
});
});
Assert.IsTrue(inputConstructedResults.SequenceEqual(output.OrderBy(i => i)));
}
}
[TestMethod]
public async Task Nested_Func()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var inputConstructedResults = input.SelectMany(i => Enumerable.Range(i * 100, TypicalThreadCount * 2));
var results = await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
return await Enumerable.Range(item * 100, TypicalThreadCount * 2)
.PMap(queue, (subItem) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
Thread.Sleep(TypicalDelayMS);
return subItem;
});
});
Assert.IsTrue(inputConstructedResults.SequenceEqual(results.SelectMany(i => i)));
}
}
[TestMethod]
public async Task Nested_Task()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var inputConstructedResults = input.SelectMany(i => Enumerable.Range(i * 100, TypicalThreadCount * 2));
var output = new List<int>();
await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Enumerable.Range(item * 100, TypicalThreadCount * 2)
.PMap(queue, async (subItem) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Task.Delay(TypicalDelayMS);
lock (output)
{
output.Add(subItem);
}
});
});
Assert.IsTrue(inputConstructedResults.SequenceEqual(output.OrderBy(i => i)));
}
}
[TestMethod]
public async Task Nested_TaskReturn()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var inputConstructedResults = input.SelectMany(i => Enumerable.Range(i * 100, TypicalThreadCount * 2));
var results = await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
return await Enumerable.Range(item * 100, TypicalThreadCount * 2)
.PMap(queue, async (subItem) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Task.Delay(TypicalDelayMS);
return subItem;
});
});
Assert.IsTrue(inputConstructedResults.SequenceEqual(results.SelectMany(i => i)));
}
}
[TestMethod]
public async Task Nested_BackgroundThreadsInvolved()
{
using (var queue = new WorkQueue(TypicalThreadCount))
{
var input = Enumerable.Range(0, TypicalThreadCount * 2).ToArray();
var inputConstructedResults = input.SelectMany(i => Enumerable.Range(i * 100, TypicalThreadCount * 2));
var results = await Enumerable.Range(0, TypicalThreadCount * 2)
.PMap(queue, async (item) =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
return await Enumerable.Range(item * 100, TypicalThreadCount * 2)
.PMap(queue, async (subItem) =>
{
return await Task.Run(async () =>
{
Assert.IsTrue(WorkQueue.WorkerThread);
await Task.Delay(TypicalDelayMS);
return subItem;
});
});
});
Assert.IsTrue(inputConstructedResults.SequenceEqual(results.SelectMany(i => i)));
}
}
}
}

View File

@ -128,8 +128,8 @@ namespace Wabbajack.Test
// Update the file and verify that it throws an error. // Update the file and verify that it throws an error.
utils.GenerateRandomFileData(game_file, 20); utils.GenerateRandomFileData(game_file, 20);
var exception = Assert.ThrowsException<AggregateException>(() => Install(compiler)); var exception = await Assert.ThrowsExceptionAsync<InvalidGameESMError>(async () => await Install(compiler));
Assert.IsInstanceOfType(exception.InnerExceptions.First(), typeof(InvalidGameESMError)); Assert.IsInstanceOfType(exception, typeof(InvalidGameESMError));
} }
[TestMethod] [TestMethod]

View File

@ -110,6 +110,7 @@
<Compile Include="MiscTests.cs" /> <Compile Include="MiscTests.cs" />
<Compile Include="MO2Tests.cs" /> <Compile Include="MO2Tests.cs" />
<Compile Include="ModlistMetadataTests.cs" /> <Compile Include="ModlistMetadataTests.cs" />
<Compile Include="PMapTests.cs" />
<Compile Include="RestartingDownloadsTests.cs" /> <Compile Include="RestartingDownloadsTests.cs" />
<Compile Include="SimpleHTTPServer.cs" /> <Compile Include="SimpleHTTPServer.cs" />
<Compile Include="TestUtils.cs" /> <Compile Include="TestUtils.cs" />

View File

@ -98,6 +98,7 @@ namespace Wabbajack.VirtualFileSystem
var allFiles = await filesToIndex var allFiles = await filesToIndex
.PMap(Queue, async f => .PMap(Queue, async f =>
{ {
Utils.Status($"Indexing {Path.GetFileName(f)}");
if (byPath.TryGetValue(f, out var found)) if (byPath.TryGetValue(f, out var found))
{ {
var fi = new FileInfo(f); var fi = new FileInfo(f);