diff --git a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ErrorClassifier.cs b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ErrorClassifier.cs
index be1509e0..de21b912 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ErrorClassifier.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ErrorClassifier.cs
@@ -26,6 +26,10 @@ public static class ErrorClassifier
///
/// Determines whether an exception represents a transient failure.
+ /// Prefer the cancellation-aware
+ /// overload at call sites that hold the caller's token — this token-less overload
+ /// classifies every as transient, including
+ /// caller-requested cancellations that must never be buffered for S&F retry.
///
/// The exception to classify.
/// for connection/timeout/cancellation exceptions; otherwise.
@@ -37,6 +41,18 @@ public static class ErrorClassifier
or OperationCanceledException;
}
+ ///
+ /// Cancellation-aware overload: a caller-requested cancellation is NOT transient
+ /// (buffering caller-abandoned work into S&F would be wrong). Prefer this overload
+ /// at call sites that hold the caller's token.
+ ///
+ public static bool IsTransient(Exception exception, CancellationToken cancellationToken)
+ {
+ if (exception is OperationCanceledException && cancellationToken.IsCancellationRequested)
+ return false;
+ return IsTransient(exception);
+ }
+
///
/// Creates a TransientException for S&F buffering.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
index 6904fb9b..9144b0e7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
@@ -340,7 +340,7 @@ public class ExternalSystemClient : IExternalSystemClient
throw ErrorClassifier.AsTransient(
$"Timeout calling {system.Name} after {effectiveTimeout.TotalSeconds:0.##}s", ex);
}
- catch (Exception ex) when (ErrorClassifier.IsTransient(ex))
+ catch (Exception ex) when (ErrorClassifier.IsTransient(ex, cancellationToken))
{
throw ErrorClassifier.AsTransient($"Connection error to {system.Name}: {ex.Message}", ex);
}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway.Tests/ErrorClassifierTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway.Tests/ErrorClassifierTests.cs
index d61ce571..7a548c6e 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway.Tests/ErrorClassifierTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway.Tests/ErrorClassifierTests.cs
@@ -64,4 +64,13 @@ public class ErrorClassifierTests
Assert.IsType(ex);
Assert.Equal("test message", ex.Message);
}
+
+ [Fact]
+ public void IsTransient_CallerCancelledOce_IsNotTransient()
+ {
+ var cts = new CancellationTokenSource();
+ cts.Cancel();
+ Assert.False(ErrorClassifier.IsTransient(new OperationCanceledException(), cts.Token));
+ Assert.True(ErrorClassifier.IsTransient(new OperationCanceledException(), CancellationToken.None));
+ }
}