dotnet/runtime · error · Error

OperationCanceledException

Error message

OperationCanceledException

What it means

Thrown by http_wasm_get_streamed_response_bytes in http.ts when the response body reader reports done (currentStreamReaderChunk.done) and controller.isAborted is true. This converts an abort-after-stream-end into an OperationCanceledException so managed code sees the cancellation rather than a clean zero-byte read.

Solutions

  1. Catch OperationCanceledException (or its HttpRequestException wrapper) on the managed streaming call and treat it as cancellation, not a fault.
  2. Avoid disposing HttpClient or cancelling the token while a streamed read loop is in progress unless you intend to cancel.
  3. If you need partial results, copy bytes read before the cancellation into your own buffer.

Example fix

// before: unhandled cancellation on streamed read
try { await foreach (var b in response.Content.ReadAsStreamAsync()) { ... } }
catch { throw; }
// after: handle cancellation explicitly
try { await foreach (var b in stream.WithCancellation(token)) { ... } }
catch (OperationCanceledException) { /* expected on abort */ }
Defensive patterns

Strategy: try-catch

Validate before calling

// pass the token and check before each read
if (token.IsCancellationRequested) return;

Try / catch

try {
  await foreach (var chunk in response.Content.ReadAsStreamAsync(token)) { ... }
} catch (OperationCanceledException) {
  // the streamed response was aborted; this is expected on cancellation
}

Prevention

When it happens

Trigger: Reading a streamed HTTP response with HttpClient.SendAsync(HttpCompletionOption.ResponseHeadersRead) and the CancellationToken is cancelled, or HttpClient is disposed, while the response body is still being drained; the server completes the stream exactly when the client aborts.

Common situations: User navigates away during a large download; a CancellationTokenSource cancels a long streaming GET; component dispose cancels an in-flight HttpClient call.

Related errors


AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10). Data as JSON: /api/errors/cfab00f64b10808b. Report an issue: GitHub.

Appendix: source

Thrown at src/mono/browser/runtime/http.ts:260

    const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte);
    return wrap_as_cancelable_promise(async () => {
        await controller.responsePromise;
        mono_assert(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();
            mute_unhandledrejection(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 remaining_source = controller.currentStreamReaderChunk.value.byteLength - controller.currentBufferOffset;
        mono_assert(remaining_source > 0, "expected remaining_source to be greater than 0");

        const bytes_copied = Math.min(remaining_source, view.byteLength);
        const source_view = controller.currentStreamReaderChunk.value.subarray(controller.currentBufferOffset, controller.currentBufferOffset + bytes_copied);
        view.set(source_view, 0);
        controller.currentBufferOffset += bytes_copied;
        if (remaining_source == bytes_copied) {
            controller.currentStreamReaderChunk = undefined;
        }

        return bytes_copied;
    });
}

View on GitHub (pinned to 60108ba66e)