From cb97a86c9f715f9bc3eb1146a049113a11d8eaa4 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Wed, 20 May 2026 18:22:47 -0500 Subject: [PATCH] fix: drop net48-incompatible TimeoutException filter in timeout catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The when filter (ex.InnerException is TimeoutException) only matches on .NET 5+. On .NET Framework 4.8 — which CI exercises via the test project's net48 target — HttpClient.Timeout throws a bare TaskCanceledException with no inner exception, so CI failed: Expected: typeof(PSProxmoxVE.Core.Exceptions.PveApiException) Actual: typeof(System.Threading.Tasks.TaskCanceledException) ---- System.Threading.Tasks.TaskCanceledException : A task was canceled. PveHttpClient.SendAsync never passes a CancellationToken to the inner HttpClient.SendAsync, so the only way a TaskCanceledException can reach this catch is HttpClient.Timeout firing — true on net48, .NET Core, and .NET 5+. Drop the filter and wrap unconditionally. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/PSProxmoxVE.Core/Client/PveHttpClient.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs index 7edc624..3729b10 100644 --- a/src/PSProxmoxVE.Core/Client/PveHttpClient.cs +++ b/src/PSProxmoxVE.Core/Client/PveHttpClient.cs @@ -353,11 +353,12 @@ namespace PSProxmoxVE.Core.Client { response = await _httpClient.SendAsync(request).ConfigureAwait(false); } - catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) + catch (TaskCanceledException ex) { - // HttpClient.Timeout elapsed. In .NET 5+, HttpClient surfaces transport - // timeouts as TaskCanceledException with a TimeoutException inner — - // distinguishing them from caller-driven CancellationToken cancellation. + // PveHttpClient.SendAsync passes no CancellationToken to HttpClient.SendAsync, + // so a TaskCanceledException reaching here can only be HttpClient.Timeout + // firing — on .NET Framework, on .NET Core, and on .NET 5+ (where it also + // carries a TimeoutException inner). Wrap it uniformly across frameworks. var seconds = _httpClient.Timeout == System.Threading.Timeout.InfiniteTimeSpan ? "infinite" : _httpClient.Timeout.TotalSeconds.ToString("0", System.Globalization.CultureInfo.InvariantCulture) + "s";