{"record":{"id":"20a5b2d2a518f0bc","repo":"dotnet/runtime","slug":"operationcanceledexception-20a5b2","errorCode":null,"errorMessage":"OperationCanceledException","messagePattern":"OperationCanceledException","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts","lineNumber":262,"sourceCode":"    const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte);\n    return wrapAsCancelablePromise(async () => {\n        await controller.responsePromise;\n        dotnetAssert.check(controller.response, \"expected response\");\n        if (!controller.response.body) {\n            // in FF when the verb is HEAD, the body is null\n            return 0;\n        }\n        if (!controller.streamReader) {\n            controller.streamReader = controller.response.body.getReader();\n            muteUnhandledRejection(controller.streamReader.closed);\n        }\n        if (!controller.currentStreamReaderChunk || controller.currentBufferOffset === undefined) {\n            controller.currentStreamReaderChunk = await controller.streamReader.read();\n            controller.currentBufferOffset = 0;\n        }\n        if (controller.currentStreamReaderChunk.done) {\n            if (controller.isAborted) {\n                throw new Error(\"OperationCanceledException\");\n            }\n            return 0;\n        }\n\n        const remainingSource = controller.currentStreamReaderChunk.value.byteLength - controller.currentBufferOffset;\n        dotnetAssert.check(remainingSource > 0, \"expected remainingSource to be greater than 0\");\n\n        const bytesCopied = Math.min(remainingSource, view.byteLength);\n        const sourceView = controller.currentStreamReaderChunk.value.subarray(controller.currentBufferOffset, controller.currentBufferOffset + bytesCopied);\n        view.set(sourceView, 0);\n        controller.currentBufferOffset += bytesCopied;\n        if (remainingSource == bytesCopied) {\n            controller.currentStreamReaderChunk = undefined;\n        }\n\n        return bytesCopied;\n    });\n}","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/dotnet/runtime/blob/290d5ab72cc1102813fbc0406fb186ceadabc340/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts#L244-L280","documentation":"While reading a streamed response in httpGetStreamedResponseBytes, once the reader reports chunk.done, if controller.isAborted is true the interop throws 'OperationCanceledException' so the .NET caller receives a proper cancellation rather than a clean zero-byte EOF. This maps to .NET's OperationCanceledException semantics.","triggerScenarios":"The response stream completed (or was cut) at the same moment the request was aborted via httpAbort/AbortController, so the interop surfaces cancellation to the .NET HttpClient.","commonSituations":"A CancellationToken fires exactly as the download stream ends; a client-side timeout triggering near response completion; explicit user cancel of a streaming download.","solutions":["Treat this as expected cancellation — handle OperationCanceledException in .NET rather than as a fault.","If unexpected, check for an overly aggressive CancellationTokenSource timeout.","Avoid aborting a controller whose response stream has already completed."],"exampleFix":"// before: cancellation surfaces as an unhandled error\n\n// after: handle expected cancellation\ntry { await responseStream.ReadAsync(buffer, ct); }\ncatch (OperationCanceledException) when (ct.IsCancellationRequested) { /* expected */ }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await responseStream.ReadAsync(buffer, ct); }\ncatch (OperationCanceledException) when (ct.IsCancellationRequested) {\n  // expected cancellation, not a fault\n}","preventionTips":["Distinguish cancellation from real errors in HttpClient exception handling.","Do not fire cancellations spuriously near stream completion.","Size CancellationTokenSource timeouts so they don't trigger right as a stream ends."],"tags":["http","streaming","cancellation","abort"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}