{"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/60108ba66eb7d1d12f595480091b4ad80a24b172/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts#L244-L280","documentation":"In httpGetStreamedResponseBytes(), after the response stream reports done (currentStreamReaderChunk.done is true) the code checks controller.isAborted. If the request was aborted (httpAbort was called, or the AbortController fired) it throws this 'OperationCanceledException' string so the .NET side recognizes it as a cancellation rather than a clean end-of-stream. It is the normal control-flow signal for an aborted streaming read.","triggerScenarios":"Reading streamed response bytes via HttpClient after the request was cancelled: the AbortController fired, httpAbort() set isAborted, and the reader then reached done. The next read call surfaces this as OperationCanceledException so the .NET Task transitions to Canceled.","commonSituations":"A CancellationToken on HttpClient.SendAsync fired mid-response; the user navigated away/cancelled a long download; a timeout aborted the fetch; cooperative cancellation during streaming response processing.","solutions":["Handle OperationCanceledException / TaskCanceledException in the C# caller as expected cancellation (often benign) rather than a hard error.","If the cancellation is unexpected, audit the CancellationTokenSource lifetime and timeout to confirm it isn't firing prematurely.","Do not retry automatically on cancellation unless the token was a transient timeout you intentionally want to retry around.","Ensure httpAbort() isn't being called by unrelated shutdown logic (e.g. disposal) that aborts in-flight responses."],"exampleFix":"// before\try { await ReadAllBytesAsync(httpClient, url, ct); }\ncatch (Exception ex) { log.Error(\"download failed\", ex); } // mislabels cancellation\n\n// after\ntry { await ReadAllBytesAsync(httpClient, url, ct); }\ncatch (OperationCanceledException) when (ct.IsCancellationRequested) {\n    // expected cancellation, no error\n}","handlingStrategy":"try-catch","validationCode":"// On the C# side, check the token before/at the read boundary.\nif (ct.IsCancellationRequested) {\n  // skip the read; do not invoke the streaming read after abort\n  return;\n}","typeGuard":"null","tryCatchPattern":"try {\n  await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);\n} catch (OperationCanceledException) when (ct.IsCancellationRequested) {\n  // expected cancellation from our token; not an error\n}","preventionTips":["Handle OperationCanceledException/TaskCanceledException as expected cancellation, not a hard failure.","Audit CancellationTokenSource lifetime/timeouts so they don't fire prematurely.","Do not retry automatically on cancellation unless the token was a transient timeout you control.","Ensure httpAbort() isn't triggered by unrelated disposal logic on in-flight responses."],"tags":["http","cancellation","streaming","abort","fetch"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}