feat(esg): honor per-system TimeoutSeconds in InvokeHttpAsync (falls back to DefaultHttpTimeout)

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 04:05:27 -04:00
parent 899af63b2a
commit 86d1a5cbf2
2 changed files with 39 additions and 8 deletions
@@ -297,12 +297,16 @@ public class ExternalSystemClient : IExternalSystemClient
}
}
// Enforce the per-call timeout. ExternalSystemDefinition has no per-system
// Timeout field yet, so the configured DefaultHttpTimeout is the effective
// round-trip limit (the design's "timeout applies to the HTTP request
// round-trip" guarantee). A linked CTS lets us distinguish a timeout from a
// caller-initiated cancellation: only the timeout is reclassified as transient.
using var timeoutCts = new CancellationTokenSource(_options.DefaultHttpTimeout);
// Enforce the per-call timeout (the design's "timeout applies to the HTTP
// request round-trip" guarantee). Resolution order: a positive per-system
// ExternalSystemDefinition.TimeoutSeconds wins; otherwise the configured
// DefaultHttpTimeout is the effective round-trip limit. A linked CTS lets us
// distinguish a timeout from a caller-initiated cancellation: only the
// timeout is reclassified as transient.
var effectiveTimeout = system.TimeoutSeconds > 0
? TimeSpan.FromSeconds(system.TimeoutSeconds)
: _options.DefaultHttpTimeout;
using var timeoutCts = new CancellationTokenSource(effectiveTimeout);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken, timeoutCts.Token);
@@ -320,7 +324,7 @@ public class ExternalSystemClient : IExternalSystemClient
{
// Our own timeout elapsed — a transient failure per the design.
throw ErrorClassifier.AsTransient(
$"Timeout calling {system.Name} after {_options.DefaultHttpTimeout.TotalSeconds:0.##}s", ex);
$"Timeout calling {system.Name} after {effectiveTimeout.TotalSeconds:0.##}s", ex);
}
catch (Exception ex) when (ErrorClassifier.IsTransient(ex))
{
@@ -343,7 +347,7 @@ public class ExternalSystemClient : IExternalSystemClient
catch (OperationCanceledException ex) when (timeoutCts.IsCancellationRequested)
{
throw ErrorClassifier.AsTransient(
$"Timeout reading response from {system.Name} after {_options.DefaultHttpTimeout.TotalSeconds:0.##}s", ex);
$"Timeout reading response from {system.Name} after {effectiveTimeout.TotalSeconds:0.##}s", ex);
}
if (response.IsSuccessStatusCode)