HCR064¶
Use cancellation-aware HTTP APIs when a token is available.
Why¶
Outbound HTTP calls that omit an available CancellationToken can keep sockets, retries, streams, and request work alive after the caller has already timed out or disconnected. Passing the token lets the call stop promptly and keeps cancellation behavior consistent through the outbound path.
Bad¶
public Task<HttpResponseMessage> GetAsync(
HttpClient client,
CancellationToken cancellationToken)
{
return client.GetAsync("https://example.com");
}
public Task<string> ReadAsync(
HttpResponseMessage response,
CancellationToken cancellationToken)
{
return response.Content.ReadAsStringAsync();
}
Better¶
public Task<HttpResponseMessage> GetAsync(
HttpClient client,
CancellationToken cancellationToken)
{
return client.GetAsync("https://example.com", cancellationToken);
}
public Task<string> ReadAsync(
HttpResponseMessage response,
CancellationToken cancellationToken)
{
return response.Content.ReadAsStringAsync(cancellationToken);
}
Current Detection¶
The implementation reports visible HttpClient calls such as synchronous Send(...), GetAsync(...), PostAsync(...), SendAsync(...), and System.Net.Http.Json's DeleteFromJsonAsync<T>(), GetFromJsonAsync<T>(), GetFromJsonAsAsyncEnumerable<T>(), PostAsJsonAsync<T>(), PutAsJsonAsync<T>(), and PatchAsJsonAsync<T>(), plus common synchronous or asynchronous HttpContent read calls such as ReadAsStream(), ReadAsStringAsync(), ReadFromJsonAsync<T>(), and ReadFromJsonAsAsyncEnumerable<T>() when a CancellationToken or CancellationTokenSource parameter or prior local is visible in the containing method, lambda, or block and the invocation does not pass a token argument.
Receivers and resolved method owners are validated with Roslyn type information when available, with syntactic fallback for unresolved snippets that visibly declare an HttpClient receiver. Calls that already pass a usable CancellationToken, calls without any visible token in scope, APIs without a token overload, resolved custom HttpClient lookalikes, and unrelated custom extensions on HttpClient or HttpContent are skipped. Passing CancellationToken.None, default, default(CancellationToken), an explicit token cast around those values, or an order-resolved local alias of those sentinel values does not suppress the diagnostic when a real token is available. A later assignment of a real token to that local is respected.
The code fix passes an available token as a named argument using the resolved cancellation-token parameter name when the analyzer has found a cancellation-aware overload, including the Token property of a visible CancellationTokenSource. It replaces an existing non-canceling sentinel or default token argument instead of appending a duplicate. When multiple token locals or parameters are in scope, it offers a distinct action for each token so the caller can select the intended cancellation boundary.
Suppression¶
Suppress only when the operation must intentionally outlive the caller's cancellation boundary or when a token is managed by a surrounding framework layer that is not visible to the analyzer.