diff --git a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs
index 5b806218f..eba0bafa3 100644
--- a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs
+++ b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs
@@ -424,11 +424,11 @@ partial class BackendManager
// Refresh DNS name if we fail to connect in order to prevent issues with incorrect DNS entries
var dnsFailure = Library.Utility.ExceptionExtensions.FlattenException(ex)
- .Any(x =>
- (x is System.Net.WebException wex && wex.Status == System.Net.WebExceptionStatus.NameResolutionFailure)
- ||
- (x is System.Net.Sockets.SocketException sockEx && sockEx.SocketErrorCode == System.Net.Sockets.SocketError.HostNotFound)
- );
+ .Any(x =>
+ (x is System.Net.WebException wex && wex.Status == System.Net.WebExceptionStatus.NameResolutionFailure)
+ ||
+ (x is System.Net.Sockets.SocketException sockEx && sockEx.SocketErrorCode == System.Net.Sockets.SocketError.HostNotFound)
+ );
if (dnsFailure)
{
try
@@ -459,10 +459,17 @@ partial class BackendManager
if (!recovered && retries <= maxRetries && retryDelay.Ticks != 0)
{
var delay = Library.Utility.Utility.GetRetryDelay(retryDelay, retries, retryWithExponentialBackoff);
- await Task.Delay(delay, context.TaskReader.ProgressToken).ConfigureAwait(false);
+ using var ct = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, context.TaskReader.ProgressToken, context.TaskReader.TransferToken, context.TaskReader.StopToken);
+ await Task.Delay(delay, ct.Token).ConfigureAwait(false);
}
}
+ if (!await context.TaskReader.ProgressRendevouz())
+ {
+ op.SetCancelled();
+ return;
+ }
+
} while (retries <= maxRetries);
// If we have a last exception, we failed
diff --git a/Duplicati/Library/Main/Operation/Common/TaskControl.cs b/Duplicati/Library/Main/Operation/Common/TaskControl.cs
index 1c02eb455..cd35da405 100644
--- a/Duplicati/Library/Main/Operation/Common/TaskControl.cs
+++ b/Duplicati/Library/Main/Operation/Common/TaskControl.cs
@@ -1,22 +1,22 @@
-// Copyright (C) 2025, The Duplicati Team
-// https://duplicati.com, hello@duplicati.com
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// Copyright (C) 2025, The Duplicati Team
+// https://duplicati.com, hello@duplicati.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
@@ -52,6 +52,11 @@ namespace Duplicati.Library.Main.Operation.Common
/// true if the transfer should continue, false if it should stop
Task TransferRendevouz();
+ ///
+ /// A cancellation token that can be used to monitor stop requests
+ ///
+ CancellationToken StopToken { get; }
+
#if DEBUG
///
/// Callback for testing the task control
@@ -142,6 +147,11 @@ namespace Duplicati.Library.Main.Operation.Common
///
private readonly CancellationTokenSource m_transferTcs = new();
+ ///
+ /// Cancellation token source that is cancelled if the operation is stopped
+ ///
+ private readonly CancellationTokenSource m_stopTcs = new CancellationTokenSource();
+
///
/// The control lock instance
///
@@ -167,6 +177,11 @@ namespace Duplicati.Library.Main.Operation.Common
///
public CancellationToken TransferToken => m_transferTcs.Token;
+ ///
+ /// A cancellation token that is cancelled if the operation is stopped
+ ///
+ public CancellationToken StopToken => m_stopTcs.Token;
+
///
/// Initializes a new instance of the class.
///
@@ -269,6 +284,8 @@ namespace Duplicati.Library.Main.Operation.Common
m_transfer.SetResult(false);
m_transferstate = State.Stopped;
}
+
+ m_stopTcs.Cancel();
}
}
@@ -298,6 +315,8 @@ namespace Duplicati.Library.Main.Operation.Common
m_transferstate = State.Terminated;
m_transferTcs.Cancel();
}
+
+ m_stopTcs.Cancel();
}
}