diff --git a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs index 62c5fd4eb..46724356d 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs @@ -191,65 +191,59 @@ partial class BackendManager } /// - /// Reclaims completed downloads + /// Reclaims completed tasks /// + /// The list of tasks to reclaim /// An awaitable task - private async Task ReclaimCompletedDownloads() + private static async Task ReclaimCompletedTasks(List tasks) { - for (int i = activeDownloads.Count - 1; i >= 0; i--) + for (int i = tasks.Count - 1; i >= 0; i--) { - if (activeDownloads[i].IsCompleted) + if (tasks[i].IsCompleted) { + var t = tasks[i]; + tasks.RemoveAt(i); // Make sure the task is awaited so we capture any exceptions - await activeDownloads[i].ConfigureAwait(false); - activeDownloads.RemoveAt(i); + await t.ConfigureAwait(false); } } } /// - /// Reclaims completed uploads + /// Reclaims completed tasks from uploads and downloads /// /// An awaitable task - private async Task ReclaimCompletedUploads() + private async Task ReclaimCompletedTasks() { - for (int i = activeUploads.Count - 1; i >= 0; i--) + await ReclaimCompletedTasks(activeUploads); + await ReclaimCompletedTasks(activeDownloads); + } + + /// + /// Ensures that there are at most N - 1 active tasks + /// + /// The maximum number of active tasks + /// The list of active tasks + /// An awaitable task + private static async Task EnsureAtMostNActiveTasks(int n, List tasks) + { + while (tasks.Count >= n) { - if (activeUploads[i].IsCompleted) - { - // Make sure the task is awaited so we capture any exceptions - await activeUploads[i].ConfigureAwait(false); - activeUploads.RemoveAt(i); - } + await Task.WhenAny(tasks).ConfigureAwait(false); + await ReclaimCompletedTasks(tasks).ConfigureAwait(false); } } /// - /// Ensures that there are at most N - 1 active downloads + /// Ensures that there are at most N - 1 active tasks /// - /// - /// - private async Task EnsureAtMostNActiveDownloads(int n) + /// The number of active uploads + /// The number of active downloads + /// An awaitable task + private async Task EnsureAtMostNActiveTasks(int uploads, int downloads) { - while (activeDownloads.Count >= n) - { - await Task.WhenAny(activeDownloads).ConfigureAwait(false); - await ReclaimCompletedDownloads().ConfigureAwait(false); - } - } - - /// - /// Ensures that there are at most N - 1 active uploads - /// - /// The maximum number of active uploads - /// An awaitable task - private async Task EnsureAtMostNActiveUploads(int n) - { - while (activeUploads.Count >= n) - { - await Task.WhenAny(activeUploads).ConfigureAwait(false); - await ReclaimCompletedUploads().ConfigureAwait(false); - } + await EnsureAtMostNActiveTasks(uploads, activeUploads).ConfigureAwait(false); + await EnsureAtMostNActiveTasks(downloads, activeDownloads).ConfigureAwait(false); } /// @@ -270,14 +264,13 @@ partial class BackendManager try { // Clean up completed uploads, if any - await ReclaimCompletedUploads().ConfigureAwait(false); + await ReclaimCompletedTasks().ConfigureAwait(false); // Allow PUT operations to be queued, if requested if (op is PutOperation putOp && !putOp.WaitForComplete) { // Wait for any active downloads to complete before starting an upload - await EnsureAtMostNActiveDownloads(1).ConfigureAwait(false); - await EnsureAtMostNActiveUploads(maxParallelUploads).ConfigureAwait(false); + await EnsureAtMostNActiveTasks(maxParallelUploads, 1).ConfigureAwait(false); // Operation is accepted into queue, so we can signal completion putOp.SetComplete(true); @@ -286,8 +279,7 @@ partial class BackendManager else if (op is GetOperation getOp) { // Wait for any active uploads to complete before starting a download - await EnsureAtMostNActiveUploads(1).ConfigureAwait(false); - await EnsureAtMostNActiveDownloads(maxParallelDownloads).ConfigureAwait(false); + await EnsureAtMostNActiveTasks(1, maxParallelDownloads).ConfigureAwait(false); // Operation is accepted into queue, so we can signal completion activeDownloads.Add(ExecuteWithRetry(getOp, tcs.Token)); @@ -295,8 +287,7 @@ partial class BackendManager else { // Wait for all of the active uploads and downloads to complete - await EnsureAtMostNActiveUploads(1).ConfigureAwait(false); - await EnsureAtMostNActiveDownloads(1).ConfigureAwait(false); + await EnsureAtMostNActiveTasks(1, 1).ConfigureAwait(false); // Execute the operation await ExecuteWithRetry(op, tcs.Token).ConfigureAwait(false); @@ -325,19 +316,30 @@ partial class BackendManager for (int i = activeUploads.Count - 1; i >= 0; i--) { var t = activeUploads[i]; - activeUploads.RemoveAt(i); - if (t.IsCompleted && !t.IsCanceled && t.Exception != null) - Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", t.Exception, "Error in active upload: {0}", t.Exception.Message); + if (t.IsCompleted) + { + activeUploads.RemoveAt(i); + if (t.IsCanceled) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", t.Exception, "Error in active upload: Cancelled"); + else if (t.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", t.Exception, "Error in active upload: {0}", t.Exception?.Message ?? "null"); + else + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "Upload was active during termination, but completed successfully"); + } + else + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "Upload was active during termination, but had state: {0}", t.Status); + } + + if (activeUploads.Count > 0) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "Terminating, but {0} active uploads are still active", activeUploads.Count); } - if (activeUploads.Count > 0) - Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "Terminating, but {0} active uploads are still active", activeUploads.Count); + // Dispose of any remaining backends + while (backendPool.TryDequeue(out var backend)) + try { backend.Dispose(); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", ex, "Failed to dispose backend instance: {0}", ex.Message); } } - - // Dispose of any remaining backends - while (backendPool.TryDequeue(out var backend)) - try { backend.Dispose(); } - catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", ex, "Failed to dispose backend instance: {0}", ex.Message); } } }