{"record":{"id":"695225ba28257d0e","repo":"dotnet/yarp","slug":"the-request-body-copy-was-canceled","errorCode":null,"errorMessage":"The request body copy was canceled.","messagePattern":"The request body copy was canceled\\.","errorType":"exception","errorClass":"OperationCanceledException","httpStatus":null,"severity":"warning","filePath":"src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs","lineNumber":206,"sourceCode":"            // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.\n            // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.\n            // Future: It may be wise to set this to true for *all* http2 incoming requests,\n            // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.\n            var (result, error) = await StreamCopier.CopyAsync(isRequest: true, _context.Request.Body, stream,\n                Headers.ContentLength ?? StreamCopier.UnknownLength, _timeProvider, _activityToken, _isStreamingRequest, cancellationToken);\n            _tcs.TrySetResult((result, error));\n\n            // Check for errors that weren't the result of the destination failing.\n            // We have to throw something here so the transport knows the body is incomplete.\n            // We can't re-throw the original exception since that would cause concurrency issues.\n            // We need to wrap it.\n            if (result == StreamCopyResult.InputError)\n            {\n                throw new IOException(\"An error occurred when reading the request body from the client.\", error);\n            }\n            if (result == StreamCopyResult.Canceled)\n            {\n                throw new OperationCanceledException(\"The request body copy was canceled.\", error);\n            }\n        }\n        finally\n        {\n            linkedCts?.Dispose();\n        }\n    }\n\n    // this is used internally by HttpContent.ReadAsStreamAsync(...)\n    protected override Task<Stream> CreateContentReadStreamAsync()\n    {\n        // Nobody should be calling this...\n        throw new NotImplementedException();\n    }\n\n    protected override bool TryComputeLength(out long length)\n    {\n        // We can't know the length of the content being pushed to the output stream.","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/dotnet/yarp/blob/bd11867bee7df522e7fd3effb08a9c85fd616908/src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs#L188-L224","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()`."],"exampleFix":"// before — default 100s timeout may cancel slow uploads\nvar config = ForwarderRequestConfig.Empty;\n// after — extended timeout for large-file-upload or gRPC streaming routes\nvar config = new ForwarderRequestConfig\n{\n    ActivityTimeout = TimeSpan.FromMinutes(10)\n};\n// or for gRPC/streaming with no idle timeout\nvar grpcConfig = new ForwarderRequestConfig\n{\n    ActivityTimeout = Timeout.InfiniteTimeSpan\n};","handlingStrategy":"try-catch","validationCode":"// Ensure ActivityTimeout is appropriate for the workload type\nvar timeout = requestConfig?.ActivityTimeout ?? TimeSpan.FromSeconds(100);\nif (isStreamingRoute && timeout < TimeSpan.FromMinutes(5))\n{\n    logger.LogWarning(\"Activity timeout {Timeout} may be too short for streaming routes\", timeout);\n    requestConfig = requestConfig with { ActivityTimeout = Timeout.InfiniteTimeSpan };\n}","typeGuard":"// No type guard — cancellation is a runtime condition, not a type issue.","tryCatchPattern":"try { await forwarder.SendAsync(context, prefix, client, config, transformer, ct); }\ncatch (OperationCanceledException ex) when (ex.Message.Contains(\"request body copy was canceled\"))\n{\n    if (!context.Response.HasStarted)\n    {\n        context.Response.StatusCode = context.RequestAborted.IsCancellationRequested\n            ? 499  // Client Closed Request (nginx convention)\n            : StatusCodes.Status504GatewayTimeout;\n    }\n}","preventionTips":["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`."],"tags":["network","request-body","timeout","cancellation","streaming","grpc"],"backgroundTag":null,"analyzedSha":"bd11867bee7df522e7fd3effb08a9c85fd616908","analyzedAt":"2026-08-13T21:29:49.359Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}