fix: add -TimeoutSeconds for long-running HTTP calls

PveHttpClient was constructed without setting HttpClient.Timeout, so
.NET's 100s default applied to every request. Multi-GB ISO uploads via
Send-PveFile on a real LAN reliably tripped this with TaskCanceledException
after 100 seconds, and there was no way to override it.

- PveSession gains a Timeout (TimeSpan) property, defaulting to 100s.
- PveHttpClient accepts an optional per-instance timeout override that
  takes precedence over the session timeout.
- Connect-PveServer exposes -TimeoutSeconds to set the session default.
- Send-PveFile and Invoke-PveStorageDownload expose -TimeoutSeconds with
  a 30-minute implicit default so large uploads/downloads do not trip
  the 100s default. -TimeoutSeconds 0 means Timeout.InfiniteTimeSpan.

Tracked as F087. Closes #59.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-05-20 18:08:26 -05:00
parent f2925ba49f
commit f3b06171b2
12 changed files with 256 additions and 9 deletions
+10 -2
View File
@@ -37,7 +37,12 @@ namespace PSProxmoxVE.Core.Client
/// Creates an HTTP client authenticated with the specified PVE session.
/// </summary>
/// <param name="session">The authenticated PVE session providing credentials and base URL.</param>
public PveHttpClient(PveSession session)
/// <param name="timeoutOverride">
/// Optional per-instance timeout override. When supplied, takes precedence over
/// <see cref="PveSession.Timeout"/>. Pass <see cref="System.Threading.Timeout.InfiniteTimeSpan"/>
/// to disable the timeout entirely (useful for multi-GB uploads/downloads).
/// </param>
public PveHttpClient(PveSession session, TimeSpan? timeoutOverride = null)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
_baseUrl = session.BaseUrl;
@@ -49,6 +54,7 @@ namespace PSProxmoxVE.Core.Client
(HttpRequestMessage _, X509Certificate2 _, X509Chain _, SslPolicyErrors _) => true;
}
_httpClient = new HttpClient(handler);
_httpClient.Timeout = timeoutOverride ?? session.Timeout;
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
@@ -58,7 +64,7 @@ namespace PSProxmoxVE.Core.Client
/// Creates a bare HTTP client for pre-session use (e.g. initial authentication).
/// No auth headers are added to requests made with this constructor.
/// </summary>
internal PveHttpClient(string hostname, int port, bool skipCertificateCheck)
internal PveHttpClient(string hostname, int port, bool skipCertificateCheck, TimeSpan? timeout = null)
{
if (string.IsNullOrWhiteSpace(hostname))
throw new ArgumentException("Hostname cannot be null or empty.", nameof(hostname));
@@ -73,6 +79,8 @@ namespace PSProxmoxVE.Core.Client
(HttpRequestMessage _, X509Certificate2 _, X509Chain _, SslPolicyErrors _) => true;
}
_httpClient = new HttpClient(handler);
if (timeout.HasValue)
_httpClient.Timeout = timeout.Value;
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));