Skip to content

Commit c5e7464

Browse files
authored
don't set a finalizer on cloned request (nodejs#4320)
1 parent 94f3c52 commit c5e7464

5 files changed

Lines changed: 34 additions & 29 deletions

File tree

lib/web/fetch/body.js

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,12 @@ try {
2929
const textEncoder = new TextEncoder()
3030
function noop () {}
3131

32-
const hasFinalizationRegistry = globalThis.FinalizationRegistry
33-
let streamRegistry
34-
35-
if (hasFinalizationRegistry) {
36-
streamRegistry = new FinalizationRegistry((weakRef) => {
37-
const stream = weakRef.deref()
38-
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
39-
stream.cancel('Response object has been garbage collected').catch(noop)
40-
}
41-
})
42-
}
32+
const streamRegistry = new FinalizationRegistry((weakRef) => {
33+
const stream = weakRef.deref()
34+
if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) {
35+
stream.cancel('Response object has been garbage collected').catch(noop)
36+
}
37+
})
4338

4439
/**
4540
* Extract a body with type from a byte sequence or BodyInit object
@@ -306,17 +301,13 @@ function safelyExtractBody (object, keepalive = false) {
306301
return extractBody(object, keepalive)
307302
}
308303

309-
function cloneBody (instance, body) {
304+
function cloneBody (body) {
310305
// To clone a body body, run these steps:
311306

312307
// https://fetch.spec.whatwg.org/#concept-body-clone
313308

314309
// 1. Let « out1, out2 » be the result of teeing body’s stream.
315-
const [out1, out2] = body.stream.tee()
316-
317-
if (hasFinalizationRegistry) {
318-
streamRegistry.register(instance, new WeakRef(out1))
319-
}
310+
const { 0: out1, 1: out2 } = body.stream.tee()
320311

321312
// 2. Set body’s stream to out1.
322313
body.stream = out1
@@ -548,6 +539,5 @@ module.exports = {
548539
cloneBody,
549540
mixinBody,
550541
streamRegistry,
551-
hasFinalizationRegistry,
552542
bodyUnusable
553543
}

lib/web/fetch/dispatcher-weakref.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

lib/web/fetch/request.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body')
66
const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers')
7-
const { FinalizationRegistry } = require('./dispatcher-weakref')()
87
const util = require('../../core/util')
98
const nodeUtil = require('node:util')
109
const {
@@ -937,7 +936,7 @@ function cloneRequest (request) {
937936
// 2. If request’s body is non-null, set newRequest’s body to the
938937
// result of cloning request’s body.
939938
if (request.body != null) {
940-
newRequest.body = cloneBody(newRequest, request.body)
939+
newRequest.body = cloneBody(request.body)
941940
}
942941

943942
// 3. Return newRequest.

lib/web/fetch/response.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22

33
const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
4-
const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require('./body')
4+
const { extractBody, cloneBody, mixinBody, streamRegistry, bodyUnusable } = require('./body')
55
const util = require('../../core/util')
66
const nodeUtil = require('node:util')
77
const { kEnumerableProperty } = util
@@ -352,7 +352,9 @@ function cloneResponse (response) {
352352
// 3. If response’s body is non-null, then set newResponse’s body to the
353353
// result of cloning response’s body.
354354
if (response.body != null) {
355-
newResponse.body = cloneBody(newResponse, response.body)
355+
newResponse.body = cloneBody(response.body)
356+
357+
streamRegistry.register(newResponse, new WeakRef(response.body.stream))
356358
}
357359

358360
// 4. Return newResponse.
@@ -552,7 +554,7 @@ function fromInnerResponse (innerResponse, guard) {
552554
setHeadersList(headers, innerResponse.headersList)
553555
setHeadersGuard(headers, guard)
554556

555-
if (hasFinalizationRegistry && innerResponse.body?.stream) {
557+
if (innerResponse.body?.stream) {
556558
// If the target (response) is reclaimed, the cleanup callback may be called at some point with
557559
// the held value provided for it (innerResponse.body.stream). The held value can be any value:
558560
// a primitive or an object, even undefined. If the held value is an object, the registry keeps

test/fetch/fire-and-forget.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,33 @@
33
const { randomFillSync } = require('node:crypto')
44
const { setTimeout: sleep } = require('node:timers/promises')
55
const { test } = require('node:test')
6-
const { fetch, Agent, setGlobalDispatcher } = require('../..')
6+
const { fetch, Request, Agent, setGlobalDispatcher } = require('../..')
77
const { createServer } = require('node:http')
88
const { closeServerAsPromise } = require('../utils/node-http')
9+
const assert = require('node:assert')
910

1011
const blob = randomFillSync(new Uint8Array(1024 * 512))
1112

1213
const hasGC = typeof global.gc !== 'undefined'
1314

15+
// https://github.com/nodejs/undici/issues/4150
16+
test('test finalizer cloned request', async () => {
17+
if (!hasGC) {
18+
throw new Error('gc is not available. Run with \'--expose-gc\'.')
19+
}
20+
21+
const request = new Request('http://localhost', { method: 'POST', body: 'Hello' })
22+
23+
for (let i = 0; i < 800; ++i) request.clone()
24+
25+
await sleep(50)
26+
27+
// eslint-disable-next-line no-undef
28+
gc(true)
29+
30+
assert.strictEqual(request.bodyUsed, false)
31+
})
32+
1433
test('does not need the body to be consumed to continue', { timeout: 180_000 }, async (t) => {
1534
if (!hasGC) {
1635
throw new Error('gc is not available. Run with \'--expose-gc\'.')

0 commit comments

Comments
 (0)