Visitar URL original
Use FinalizationRegistry to dump the socket if the Response is GC'd · Issue #2856 · nodejs/undici · GitHub
Skip to content

Use FinalizationRegistry to dump the socket if the Response is GC'd #2856

Closed

Description

@mcollina

If a user can't consume the body anymore, we can just dump it.
We can detect a response is not accessible by the use of a FinalizationRegistry.

@KhafraDev I think this would bring us a step closer to the spec.

Activity

  1. mcollina commented on Feb 26, 2024

    @mcollina
    SponsorMemberAuthor

    I thought this was implemented in this way but I could not substantiate it with code.

  2. ronag commented on Feb 26, 2024

    @ronag
    Member

    To really get it spec compliant:ish we would need force gc at some interval? Or we could run out of socket handles.

  3. KhafraDev commented on Feb 27, 2024

    @KhafraDev
    Member

    Without consuming the body I couldn't get node to gc the Response returned from fetch

  4. metcoder95 commented on Feb 27, 2024

    @metcoder95
    Member

    Without consuming the body I couldn't get node to gc the Response returned from fetch

    And that happens due to WebStreams or also with normal Streams?

  5. mcollina commented on Feb 27, 2024

    @mcollina
    SponsorMemberAuthor

    Without consuming the body I couldn't get node to gc the Response returned from fetch

    I think you got it backwards. We need to use a FinalizationRegistry to detect when Response is gc'd to consume the body. I think

    let responseObject = null
    keeps a strong reference of both Request and Response of a given fetch. I think some WeakRef could allow it to be collected, and therefore GC'd.

  6. KhafraDev commented on Feb 27, 2024

    @KhafraDev
    Member

    no luck with that either, there are quite a few things that also have strong references to them (requestObject, controller, etc.). It'd be pretty messy dealing with all of them.

    diff
    diff --git a/lib/web/fetch/index.js b/lib/web/fetch/index.js
    index e7115b2e..84a7a2cb 100644
    --- a/lib/web/fetch/index.js
    +++ b/lib/web/fetch/index.js
    @@ -64,12 +64,18 @@ const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = requ
     const { getGlobalDispatcher } = require('../../global')
     const { webidl } = require('./webidl')
     const { STATUS_CODES } = require('node:http')
    +const { FinalizationRegistry } = require('./dispatcher-weakref')()
    +
     const GET_OR_HEAD = ['GET', 'HEAD']
    
     const defaultUserAgent = typeof __UNDICI_IS_NODE__ !== 'undefined' || typeof esbuildDetection !== 'undefined'
       ? 'node'
       : 'undici'
    
    +const registry = new FinalizationRegistry((response) => {
    +  console.log(response)
    +})
    +
     /** @type {import('buffer').resolveObjectURL} */
     let resolveObjectURL
    
    @@ -188,7 +194,10 @@ function fetch (input, init = undefined) {
    
           // 4. Abort the fetch() call with p, request, responseObject,
           //    and requestObject’s signal’s abort reason.
    -      abortFetch(p, request, responseObject, requestObject.signal.reason)
    +      const _responseObject = responseObject.deref()
    +      if (_responseObject) {
    +        abortFetch(p, request, responseObject, requestObject.signal.reason)
    +      }
         }
       )
    
    @@ -215,8 +224,12 @@ function fetch (input, init = undefined) {
    
           // 2. Abort the fetch() call with p, request, responseObject, and
           //    deserializedError.
    +      const _responseObject = responseObject.deref()
    +
    +      if (_responseObject) {
    +        abortFetch(p, request, _responseObject, controller.serializedAbortReason)
    +      }
    
    -      abortFetch(p, request, responseObject, controller.serializedAbortReason)
           return
         }
    
    @@ -229,7 +242,8 @@ function fetch (input, init = undefined) {
    
         // 4. Set responseObject to the result of creating a Response object,
         // given response, "immutable", and relevantRealm.
    -    responseObject = fromInnerResponse(response, 'immutable', relevantRealm)
    +    responseObject = new WeakRef(fromInnerResponse(response, 'immutable', relevantRealm))
    +    registry.register(responseObject, response)
    
         // 5. Resolve p with responseObject.
         p.resolve(responseObject)
  7. mcollina commented on Feb 27, 2024

    @mcollina
    SponsorMemberAuthor

    I'll take a look as well. How do you test it?

  8. KhafraDev commented on Feb 27, 2024

    @KhafraDev
    Member
    const { fetch } = require('./index.js')
    
    async function run () {
      await fetch('https://example.com')
    }
    
    run()
    
    // otherwise process will end
    setTimeout(() => {}, 120000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions