Interview question
Handle downstream timeout and cancellation
Separates caller cancellation from a dependency timeout and maps expected downstream outcomes safely.
TL;DR
Separates caller cancellation from a dependency timeout and maps expected downstream outcomes safely.
HttpClientFactory, per-operation timeout, cancellation propagation, response streaming, and safe error mapping.
Practice the problem like a real interview: restate, reason, implement, and test.
Call the pricing service with a two-second operation timeout. Propagate client disconnects, return 504 for the dependency timeout, and map unavailable responses without exposing response bodies.
If the caller disconnects after 100 ms, work stops. If the dependency exceeds two seconds while the caller remains connected, the API returns a stable 504 problem response.
I let the request token represent caller interest and add a narrower dependency deadline through a linked token. The exception filter distinguishes which cancellation fired, while normal downstream statuses remain explicit branches.
builder.Services.AddHttpClient<PricingClient>(client =>
{
client.BaseAddress = new Uri(configuration["Pricing:BaseUrl"]!);
client.Timeout = Timeout.InfiniteTimeSpan;
});
public sealed class PricingClient(HttpClient http)
{
public async Task<PricingResult> GetAsync(Guid productId, CancellationToken requestCt)
{
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(requestCt);
deadline.CancelAfter(TimeSpan.FromSeconds(2));
try {
using var response = await http.GetAsync($"prices/{productId}",
HttpCompletionOption.ResponseHeadersRead, deadline.Token);
if (response.StatusCode == HttpStatusCode.NotFound) return PricingResult.NotFound;
if (!response.IsSuccessStatusCode) return PricingResult.Unavailable;
var price = await response.Content.ReadFromJsonAsync<PriceResponse>(
cancellationToken: deadline.Token);
return price is null ? PricingResult.Invalid : new PricingResult.Success(price);
}
catch (OperationCanceledException) when (!requestCt.IsCancellationRequested) {
return PricingResult.TimedOut;
}
catch (HttpRequestException) {
return PricingResult.Unavailable;
}
catch (Exception ex) when (ex is JsonException or NotSupportedException) {
return PricingResult.Invalid;
}
}
}
app.MapGet("/products/{id:guid}/price", async (Guid id, PricingClient client,
CancellationToken ct) => (await client.GetAsync(id, ct)).ToHttpResult());
A finite dependency budget protects request capacity while caller cancellation avoids wasted work. Retries, if added, must fit inside the same overall deadline and target only transient, safe operations.
Next in API Implementation Labs: Durable 202 Job