dotnet/yarp · warning · OperationCanceledException
The request body copy was canceled.
Error message
The request body copy was canceled.
What it means
During the client-to-destination body copy, the operation is canceled (typically by the activity timeout firing or the request being aborted). The `StreamCopier.CopyAsync` returns `StreamCopyResult.Canceled` and YARP wraps the cancellation into an `OperationCanceledException` so the HttpClient transport aborts the outgoing request body. This is the normal path for timed-out or aborted streaming requests, not necessarily a bug.
Source
Thrown at src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs:206
// and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.
// Because the sockets aren't flushed, the perf impact of this choice is expected to be small.
// Future: It may be wise to set this to true for *all* http2 incoming requests,
// but for now, out of an abundance of caution, we only do it for requests that look like gRPC.
var (result, error) = await StreamCopier.CopyAsync(isRequest: true, _context.Request.Body, stream,
Headers.ContentLength ?? StreamCopier.UnknownLength, _timeProvider, _activityToken, _isStreamingRequest, cancellationToken);
_tcs.TrySetResult((result, error));
// Check for errors that weren't the result of the destination failing.
// We have to throw something here so the transport knows the body is incomplete.
// We can't re-throw the original exception since that would cause concurrency issues.
// We need to wrap it.
if (result == StreamCopyResult.InputError)
{
throw new IOException("An error occurred when reading the request body from the client.", error);
}
if (result == StreamCopyResult.Canceled)
{
throw new OperationCanceledException("The request body copy was canceled.", error);
}
}
finally
{
linkedCts?.Dispose();
}
}
// this is used internally by HttpContent.ReadAsStreamAsync(...)
protected override Task<Stream> CreateContentReadStreamAsync()
{
// Nobody should be calling this...
throw new NotImplementedException();
}
protected override bool TryComputeLength(out long length)
{
// We can't know the length of the content being pushed to the output stream.View on GitHub (pinned to bd11867bee)
Solutions
- If the timeout is expected for your workload (large uploads, gRPC streaming), increase `ActivityTimeout` in the cluster's `HttpRequest` config or the `ForwarderRequestConfig`.
- For gRPC and long-lived streaming connections, set the activity timeout to `System.Threading.Timeout.InfiniteTimeSpan` to disable it.
- Verify the client is not intentionally stalling — investigate client-side network issues if cancellations are unexpected.
- Handle the `OperationCanceledException` in your error pipeline and return 504 Gateway Timeout or 499 (Client Closed Request) as appropriate.
- Check that no upstream middleware or client is aborting the request prematurely via `context.Abort()`.
Example fix
// before — default 100s timeout may cancel slow uploads
var config = ForwarderRequestConfig.Empty;
// after — extended timeout for large-file-upload or gRPC streaming routes
var config = new ForwarderRequestConfig
{
ActivityTimeout = TimeSpan.FromMinutes(10)
};
// or for gRPC/streaming with no idle timeout
var grpcConfig = new ForwarderRequestConfig
{
ActivityTimeout = Timeout.InfiniteTimeSpan
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure ActivityTimeout is appropriate for the workload type
var timeout = requestConfig?.ActivityTimeout ?? TimeSpan.FromSeconds(100);
if (isStreamingRoute && timeout < TimeSpan.FromMinutes(5))
{
logger.LogWarning("Activity timeout {Timeout} may be too short for streaming routes", timeout);
requestConfig = requestConfig with { ActivityTimeout = Timeout.InfiniteTimeSpan };
} Type guard
// No type guard — cancellation is a runtime condition, not a type issue.
Try / catch
try { await forwarder.SendAsync(context, prefix, client, config, transformer, ct); }
catch (OperationCanceledException ex) when (ex.Message.Contains("request body copy was canceled"))
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = context.RequestAborted.IsCancellationRequested
? 499 // Client Closed Request (nginx convention)
: StatusCodes.Status504GatewayTimeout;
}
} Prevention
- Set `ActivityTimeout` to `Timeout.InfiniteTimeSpan` for gRPC and WebSocket routes.
- Increase the timeout for large file upload routes.
- Distinguish between client-initiated cancellation and timeout by checking `context.RequestAborted`.
When it happens
Trigger: The YARP activity timeout (default 100 seconds) elapses with no forward progress while streaming the request body (the client is too slow or stalled). Or `context.RequestAborted` fires because the client disconnected, which propagates through the linked CancellationTokenSource. The copy returns `Canceled` and line 206 throws the wrapped exception.
Common situations: A client streams a large upload very slowly (e.g., throttled mobile upload) and exceeds the 100-second activity timeout. A gRPC streaming call stalls. A client disconnects mid-upload, triggering RequestAborted. The activity timeout is set too aggressively for the workload.
Related errors
- An error occurred when reading the request body from the cli
- Failed to download {url} after {max_retries} attempts.
- Current request can't be delegated. Either the request body
- The http client must be of type HttpMessageInvoker, not Http
- SPDY requests require HTTP/1.1 support, but outbound HTTP/1.
AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13).
Data as JSON: /api/errors/695225ba28257d0e.
Report an issue: GitHub.