node icon indicating copy to clipboard operation
node copied to clipboard

Request signal isn't aborted after garbage collection

Open jvaill opened this issue 1 year ago • 9 comments

Bug Description

Passing a signal to Request() and aborting it does not cause the underlying request signal to be aborted after it's been garbage collected. This leads to issues where you want to listen on the request signal even after the request itself falls out of scope - e.g., the request is instantiated with an abort controller signal and downstream code is waiting for that signal to be aborted via request.signal.

Reproducible By

Run the following using node --expose-gc main.js:

const ac = new AbortController();

ac.signal.addEventListener("abort", () => {
  console.log("ac signal aborted");
});

const request = new Request("https://google.ca", { signal: ac.signal });

request.signal.addEventListener("abort", () => {
  console.log("request signal aborted");
});

setTimeout(() => {
  global.gc();
  ac.abort();
}, 0);

Expected Behavior

ac signal aborted and request signal aborted should be logged to the console.

Instead, only ac signal aborted is logged.

Environment

Latest node and undici. Tried it in some older versions as well.

Additional context

A few folks are running into this while trying to close event stream requests in the remix web framework. The underlying requests are never closed because the signal doesn't get aborted.

jvaill avatar Sep 25 '24 21:09 jvaill

@KhafraDev

ronag avatar Sep 26 '24 05:09 ronag

https://dom.spec.whatwg.org/#abort-signal-garbage-collection

ronag avatar Sep 26 '24 05:09 ronag

Therefore undici seems to be working properly? Since the parent AbortSignal is aborted, the dependent signal (ie. the one created for every instance of Request) can be garbage collected.

KhafraDev avatar Sep 26 '24 05:09 KhafraDev

@KhafraDev - I would expect the dependent signal to also be aborted after the parent signal is aborted & before it is garbage collected. Currently, the dependent signal seems to stop working when the request itself is garbage collected, not when its parent signal is garbage collected. Perhaps I'm missing something?

jvaill avatar Sep 26 '24 17:09 jvaill

Currently when a Request is collected, the signal is removed (see https://github.com/nodejs/undici/pull/1113)

must not be garbage collected while its source signals is non-empty and it has registered event listeners for its abort event or its abort algorithms is non-empty.

There is a bug here - we shouldn't be removing the signal if there are registered 'abort' event listeners. I will have a PR in a few hours unless the approach I'm thinking of doesn't work.

KhafraDev avatar Sep 26 '24 18:09 KhafraDev

I believe this is a bug in node -

const ac = new AbortController();

ac.signal.addEventListener("abort", () => {
  console.log("ac signal aborted");
});

const request = {
  signal: (() => {
    const ourAc = new AbortController()
    ourAc.signal.addEventListener('abort', () => console.log('called'), { once: true })
    return ourAc.signal
  })()
}

request.signal.addEventListener("abort", () => {
  console.log("request signal aborted");
});

setTimeout(() => {
  global.gc();
  ac.abort();
}, 0);

Only ac signal aborted is printed.

KhafraDev avatar Sep 27 '24 16:09 KhafraDev

@benjamingr

ronag avatar Sep 27 '24 16:09 ronag

cc @nodejs/web-standards

mcollina avatar Sep 28 '24 06:09 mcollina

transferred to node

mcollina avatar Oct 17 '24 16:10 mcollina

Is there any work being done to resolve this?

zachrip avatar Dec 20 '24 13:12 zachrip

@zachrip not at the moment, it's a bug in Node.js. Specifically https://dom.spec.whatwg.org/#abort-signal-garbage-collection is not implemented.

mcollina avatar Jan 14 '25 10:01 mcollina