From 806d3b512cd12ee6ef2f555adcd2ecd4b7b9a175 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Fri, 23 May 2025 15:48:24 +0200 Subject: [PATCH] Fixed livelock in the new restore flow by changing from a spinlock to an awaitable task. --- .../Main/Operation/Restore/FileProcessor.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs index e26215fc0..52bd07367 100644 --- a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs +++ b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs @@ -53,6 +53,8 @@ namespace Duplicati.Library.Main.Operation.Restore /// The number of file processors that are still restoring files. It is set by the . /// public static int file_processors_restoring_files; + public static object file_processor_continue_lock = new object(); + public static TaskCompletionSource file_processor_continue = new(); /// /// Runs the file processor process that restores the files that need to be restored. @@ -110,7 +112,10 @@ namespace Duplicati.Library.Main.Operation.Restore // Check if there are other FileProcessor's still restoring files if (!decremented) { - await RendezvousBeforeProcessingFolderMetadata(); + if (file_processors_restoring_files <= 0 && !file_processor_continue.Task.IsCompleted) + file_processor_continue.SetResult(); + else + await RendezvousBeforeProcessingFolderMetadata(); decremented = true; } @@ -459,7 +464,12 @@ namespace Duplicati.Library.Main.Operation.Restore finally { if (!decremented) - Interlocked.Decrement(ref file_processors_restoring_files); + lock (file_processor_continue_lock) + { + file_processors_restoring_files--; + if (file_processors_restoring_files <= 0 && !file_processor_continue.Task.IsCompleted) + file_processor_continue.SetResult(); + } block_request.Retire(); block_response.Retire(); @@ -554,11 +564,18 @@ namespace Duplicati.Library.Main.Operation.Restore /// An awaitable task that completes once all of the FileProcessor's have rendezvoused. private static async Task RendezvousBeforeProcessingFolderMetadata() { - Interlocked.Decrement(ref file_processors_restoring_files); - - while (file_processors_restoring_files > 0) + var should_wait = false; + lock (file_processor_continue_lock) { - await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + file_processors_restoring_files--; + if (file_processors_restoring_files <= 0 && !file_processor_continue.Task.IsCompleted) + file_processor_continue.SetResult(); + should_wait = file_processors_restoring_files > 0; + } + + if (should_wait) + { + await file_processor_continue.Task.ConfigureAwait(false); } }