From 065ef1f5db4fb8df0c3c09aeef0e658182e22bff Mon Sep 17 00:00:00 2001 From: psang39 Date: Thu, 3 Sep 2026 18:23:16 +0700 Subject: [PATCH] fix(deno): Propagate stream cancellation Forward cancellation from monitored response streams to their source readers so upstream producers can stop work and release resources. Fixes #23895 --- packages/deno/src/utils/streaming.ts | 3 +++ packages/deno/test/streaming.test.ts | 30 +++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/deno/src/utils/streaming.ts b/packages/deno/src/utils/streaming.ts index 045ad0735fb2..491131e6e093 100644 --- a/packages/deno/src/utils/streaming.ts +++ b/packages/deno/src/utils/streaming.ts @@ -103,5 +103,8 @@ function monitorStream( controller.close(); reader.releaseLock(); }, + cancel(reason) { + return reader.cancel(reason); + }, }); } diff --git a/packages/deno/test/streaming.test.ts b/packages/deno/test/streaming.test.ts index d9849ece3c76..cfa040ac01cf 100644 --- a/packages/deno/test/streaming.test.ts +++ b/packages/deno/test/streaming.test.ts @@ -1,6 +1,34 @@ // -import { assertEquals } from 'https://deno.land/std@0.212.0/assert/mod.ts'; +import { assertEquals, assertStrictEquals } from 'https://deno.land/std@0.212.0/assert/mod.ts'; + +import { streamResponse } from '../src/utils/streaming.ts'; + +Deno.test('cancels the source when the response reader is cancelled', async () => { + let sourceCancelReason: unknown; + + const source = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('first event')); + }, + cancel(reason) { + sourceCancelReason = reason; + }, + }); + + const span = { end() {} } as Parameters[0]; + const response = await streamResponse( + span, + new Response(source, { headers: { 'content-type': 'text/event-stream' } }), + ); + const reader = response.body!.getReader(); + const reason = new Error('client disconnected'); + + await reader.read(); + await reader.cancel(reason); + + assertStrictEquals(sourceCancelReason, reason); +}); Deno.test('reader.closed.then(f, f) suppresses rejection when releaseLock is called on an open stream', async () => { // Reproduces the bug from GitHub issue #20177: