dotnet/runtime · warning · Error
OperationCanceledException
Error message
OperationCanceledException
What it means
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.
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.
Example fix
// before ry { await ReadAllBytesAsync(httpClient, url, ct); }
catch (Exception ex) { log.Error("download failed", ex); } // mislabels cancellation
// after
try { await ReadAllBytesAsync(httpClient, url, ct); }
catch (OperationCanceledException) when (ct.IsCancellationRequested) {
// expected cancellation, no error
} Defensive patterns
Strategy: try-catch
Validate before calling
// On the C# side, check the token before/at the read boundary.
if (ct.IsCancellationRequested) {
// skip the read; do not invoke the streaming read after abort
return;
} Type guard
null
Try / catch
try {
await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
} catch (OperationCanceledException) when (ct.IsCancellationRequested) {
// expected cancellation from our token; not an error
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- BrowserHttpWriteStream.Rejected
- OperationCanceledException
- BrowserHttpWriteStream.Rejected
- Please install `node-fetch` and `node-abort-controller` npm…
- Please install `node-fetch` and `node-abort-controller` npm…
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/20a5b2d2a518f0bc.
Report an issue: GitHub.
Appendix: source
Thrown at src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts:262
const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte);
return wrapAsCancelablePromise(async () => {
await controller.responsePromise;
dotnetAssert.check(controller.response, "expected response");
if (!controller.response.body) {
// in FF when the verb is HEAD, the body is null
return 0;
}
if (!controller.streamReader) {
controller.streamReader = controller.response.body.getReader();
muteUnhandledRejection(controller.streamReader.closed);
}
if (!controller.currentStreamReaderChunk || controller.currentBufferOffset === undefined) {
controller.currentStreamReaderChunk = await controller.streamReader.read();
controller.currentBufferOffset = 0;
}
if (controller.currentStreamReaderChunk.done) {
if (controller.isAborted) {
throw new Error("OperationCanceledException");
}
return 0;
}
const remainingSource = controller.currentStreamReaderChunk.value.byteLength - controller.currentBufferOffset;
dotnetAssert.check(remainingSource > 0, "expected remainingSource to be greater than 0");
const bytesCopied = Math.min(remainingSource, view.byteLength);
const sourceView = controller.currentStreamReaderChunk.value.subarray(controller.currentBufferOffset, controller.currentBufferOffset + bytesCopied);
view.set(sourceView, 0);
controller.currentBufferOffset += bytesCopied;
if (remainingSource == bytesCopied) {
controller.currentStreamReaderChunk = undefined;
}
return bytesCopied;
});
}View on GitHub (pinned to 60108ba66e)