From d9ad45096c0135ac4e8eacddce1e826ce2d38241 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:58:36 +0000 Subject: [PATCH] fix: set finalizer only for fetch responses --- lib/web/fetch/response.js | 3 ++- test/fetch/fire-and-forget.js | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/web/fetch/response.js b/lib/web/fetch/response.js index 6b954afaab3..ffb7ce14074 100644 --- a/lib/web/fetch/response.js +++ b/lib/web/fetch/response.js @@ -242,7 +242,8 @@ class Response { const clonedResponse = cloneResponse(this.#state) // Note: To re-register because of a new stream. - if (this.#state.body?.stream) { + // Don't set finalizers other than for fetch responses. + if (this.#state.urlList.length !== 0 && this.#state.body?.stream) { streamRegistry.register(this, new WeakRef(this.#state.body.stream)) } diff --git a/test/fetch/fire-and-forget.js b/test/fetch/fire-and-forget.js index a3ea6d83bb2..34bf6c80b2f 100644 --- a/test/fetch/fire-and-forget.js +++ b/test/fetch/fire-and-forget.js @@ -46,6 +46,42 @@ test('test finalizer cloned response', async () => { await response.arrayBuffer() // check consume body }) +// https://github.com/nodejs/undici/pull/4803 +test('should not call cancel() during GC (new Response)', async () => { + if (!hasGC) { + throw new Error('gc is not available. Run with \'--expose-gc\'.') + } + + let response = new Response(new ReadableStream({ + start () {}, + + pull (ctrl) { + ctrl.enqueue(new Uint8Array([72, 101, 108, 108, 111])) // Hello + }, + + cancel () { + throw new Error('should be unreachable') + } + })) + + let cloned = response.clone() + + const body = response.body + + await nextTick() + cloned.body.cancel() + + cloned = null + response = null + + await nextTick() + // eslint-disable-next-line no-undef + gc() + + await nextTick(); // handle 'uncaughtException' event + (function () {})(body) // save a reference without triggering linter warnings +}) + test('does not need the body to be consumed to continue', { timeout: 180_000 }, async (t) => { if (!hasGC) { throw new Error('gc is not available. Run with \'--expose-gc\'.')