-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
events: remove weak listener for event target #48952
events: remove weak listener for event target #48952
Conversation
lib/internal/event_target.js
Outdated
@@ -691,6 +696,30 @@ class EventTarget { | |||
} | |||
} | |||
|
|||
// TODO - rename this function | |||
removeInternalListener(type, listenerWrapper) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make this a separate, standalone function instead of adding it to the prototype (as a non-standard function)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good question, Because it accesses the instance internals and I want to make it close to the other remove listener 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My general impression is that for web standards APIs, node tries to avoid exposing non-standard properties and functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't plan to expose it as plain function, either extracting or use symbol as a key
lib/internal/event_target.js
Outdated
while (handler !== undefined) { | ||
if (handler === listenerWrapper) { | ||
handler.remove(); | ||
root.size--; | ||
if (root.size === 0) | ||
this[kEvents].delete(type); | ||
this[kRemoveListener](root.size, type, listener, capture); | ||
break; | ||
} | ||
handler = handler.next; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking maybe just remove the while as we are sure that this exists unless there is some weird thing that can happen here...
|
||
const newMemory = getMemoryAllocatedInMB(); | ||
|
||
strictEqual(newMemory - currentMemory < 100, true, new Error(`After consuming 10 items the memory increased by ${Math.floor(newMemory - currentMemory)}MB`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to test 🤔 feels like this could be flakey
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good question but to reduce flakiness this test would be to not run in parallel with other tests, will move it to a different file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this 100 sounds like a magic number which would not be truly reliable... (I don't have a better idea of how to test this though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the test as it did not work for some reason...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rluvaton I wrote this in another conversation, but I feel it belongs here...
Maybe using a WeakRef[]
etc we can ensure the values are gone after globalThis.gc();
, instead of checking an increase in memory? (meaning add them wrapped by WeakRef
into an array during the createALotOfAbortSignals
setup)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great idea, the test was added :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed the test as it can be flaky and instead checked that the memory leak warning was not emitted...
Also cc @nodejs/events |
@@ -231,6 +233,30 @@ const { setTimeout: sleep } = require('timers/promises'); | |||
AbortSignal.timeout(1_200_000); | |||
} | |||
|
|||
{ | |||
(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure why this requires the async iife wrapper, or the sleeps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
without the sleep, the test would fail (as I assume the GC happen in a different thread when having more than 1 core)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rluvaton so is 10ms trust-worthy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used 10ms like the rest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
increased to 100ms and hope the test now pass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replaced the test with another one that check that the memory leak warning not emitted which would be faster and have the same result
@rluvaton the test seems to be failing 😕 not ok 244 parallel/test-abortcontroller
---
duration_ms: 244.52900
severity: fail
exitcode: 1
stack: |-
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ AbortSignal {
+ [Symbol(events.maxEventTargetListeners)]: 10,
+ [Symbol(events.maxEventTargetListenersWarned)]: false,
+ [Symbol(kAborted)]: false,
+ [Symbol(kComposite)]: false,
+ [Symbol(kEvents)]: SafeMap(1) [Map] {
+ 'abort' => <ref *1> {
+ next: Listener {
+ callback: SafeWeakRef [WeakRef] {},
+ flags: 81,
+ listener: SafeWeakRef [WeakRef] {},
+ next: undefined,
+ previous: [Circular *1]
+ },
+ resistStopPropagation: true,
+ size: 1
+ }
+ },
+ [Symbol(kHandlers)]: SafeMap(0) [Map] {},
+ [Symbol(kReason)]: undefined,
+ [Symbol(kTimeout)]: true
+ }
- undefined
at /home/iojs/build/workspace/node-test-commit-linux/test/parallel/test-abortcontroller.js:256:5 {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: AbortSignal {
[Symbol(kEvents)]: SafeMap(1) [Map] {
'abort' => <ref *1> {
size: 1,
next: Listener {
next: undefined,
previous: [Circular *1],
listener: SafeWeakRef [WeakRef] {},
flags: 81,
callback: SafeWeakRef [WeakRef] {}
},
resistStopPropagation: true
}
},
[Symbol(events.maxEventTargetListeners)]: 10,
[Symbol(events.maxEventTargetListenersWarned)]: false,
[Symbol(kHandlers)]: SafeMap(0) [Map] {},
[Symbol(kAborted)]: false,
[Symbol(kReason)]: undefined,
[Symbol(kComposite)]: false,
[Symbol(kTimeout)]: true
},
expected: undefined,
operator: 'strictEqual'
}
Node.js v21.0.0-pre
... |
[kWeakHandler]: {}, | ||
}); | ||
|
||
await setTimeout(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not?
await setTimeout(0); | |
await Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it won't GC with this
Landed in c39f04c |
Fixes: nodejs#48951 PR-URL: nodejs#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Fixes: nodejs#48951 PR-URL: nodejs#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Fixes: nodejs#48951 PR-URL: nodejs#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Fixes: nodejs#48951 PR-URL: nodejs#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Was this included in, or is planned to be included in v18? |
This was released just in v20.6.0, which means it will take some time to reach v18 which is LTS, but hopefully it should happen soon 🙂 |
Fixes: nodejs/node#48951 PR-URL: nodejs/node#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Fixes: nodejs/node#48951 PR-URL: nodejs/node#48952 Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Fixes: #48951