From d0c32a239d20dfc1902c1a2948897fe7404ec8a2 Mon Sep 17 00:00:00 2001 From: Anna Date: Sun, 25 Aug 2024 03:46:22 -0400 Subject: [PATCH] fix: only allow one duplication at a time Prevent simultaneous duplications from racing on a delete, causing potential file not found errors. --- DownloadTask.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DownloadTask.cs b/DownloadTask.cs index 172721e..3d180bb 100644 --- a/DownloadTask.cs +++ b/DownloadTask.cs @@ -56,6 +56,7 @@ internal class DownloadTask : IDisposable { private ConcurrentDeque Entries { get; } = new(); private Util.SentryTransaction? Transaction { get; set; } private bool SupportsHardLinks { get; set; } + private SemaphoreSlim DuplicateMutex { get; } = new(1, 1); /// /// A mapping of existing file paths to their hashes. Paths are relative. @@ -123,6 +124,7 @@ public void Dispose() { this._disposed = true; GC.SuppressFinalize(this); this.CancellationToken.Dispose(); + this.DuplicateMutex.Dispose(); } internal Task Start() { @@ -582,7 +584,7 @@ await Plugin.Resilience.ExecuteAsync( var gamePaths = neededFiles.Files.Files[hash]; var outputPaths = GetOutputPaths(gamePaths); - await DuplicateFile(filesPath, outputPaths, joined); + await this.DuplicateFile(filesPath, outputPaths, joined); this.StateData += 1; } @@ -754,6 +756,8 @@ private static string[] GetOutputPaths(IReadOnlyCollection> files) } private async Task DuplicateFile(string filesDir, IEnumerable outputPaths, string path) { + using var guard = await SemaphoreGuard.WaitAsync(this.DuplicateMutex, this.CancellationToken.Token); + if (!this.SupportsHardLinks) { // If hard links aren't supported, copy the path to the first output // path. @@ -788,6 +792,8 @@ private async Task DuplicateFile(string filesDir, IEnumerable outputPath await DuplicateInner(outputPath); } + return; + async Task DuplicateInner(string dest) { dest = Path.Join(filesDir, dest); if (path.Equals(dest, StringComparison.InvariantCultureIgnoreCase)) { @@ -896,7 +902,7 @@ await resp.Content.ReadAsStreamAsync(this.CancellationToken.Token), ); Duplicate: - await DuplicateFile(filesPath, outputPaths, validPath); + await this.DuplicateFile(filesPath, outputPaths, validPath); this.StateData += 1; }