Skip to content

Commit acd9872

Browse files
rluvatontargos
authored andcommitted
events: remove weak listener for event target
Fixes: #48951 PR-URL: #48952 Reviewed-By: Chemi Atlow <[email protected]>
1 parent a625f22 commit acd9872

File tree

3 files changed

+56
-8
lines changed

3 files changed

+56
-8
lines changed

‎lib/internal/event_target.js‎

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const kWeakHandler = Symbol('kWeak');
6262
constkResistStopPropagation=Symbol('kResistStopPropagation');
6363

6464
constkHybridDispatch=SymbolFor('nodejs.internal.kHybridDispatch');
65+
constkRemoveWeakListenerHelper=Symbol('nodejs.internal.removeWeakListenerHelper');
6566
constkCreateEvent=Symbol('kCreateEvent');
6667
constkNewListener=Symbol('kNewListener');
6768
constkRemoveListener=Symbol('kRemoveListener');
@@ -392,7 +393,7 @@ let weakListenersState = null;
392393
letobjectToWeakListenerMap=null;
393394
functionweakListeners(){
394395
weakListenersState??=newSafeFinalizationRegistry(
395-
(listener)=>listener.remove(),
396+
({ eventTarget,listener, eventType })=>eventTarget.deref()?.[kRemoveWeakListenerHelper](eventType,listener),
396397
);
397398
objectToWeakListenerMap??=newSafeWeakMap();
398399
return{registry: weakListenersState,map: objectToWeakListenerMap};
@@ -414,7 +415,7 @@ const kFlagResistStopPropagation = 1 << 6;
414415
// the linked list makes dispatching faster, even if adding/removing is
415416
// slower.
416417
classListener{
417-
constructor(previous,listener,once,capture,passive,
418+
constructor(eventTarget,eventType,previous,listener,once,capture,passive,
418419
isNodeStyleListener,weak,resistStopPropagation){
419420
this.next=undefined;
420421
if(previous!==undefined)
@@ -441,7 +442,13 @@ class Listener{
441442

442443
if(this.weak){
443444
this.callback=newSafeWeakRef(listener);
444-
weakListeners().registry.register(listener,this,this);
445+
weakListeners().registry.register(listener,{
446+
__proto__: null,
447+
// Weak ref so the listener won't hold the eventTarget alive
448+
eventTarget: newSafeWeakRef(eventTarget),
449+
listener: this,
450+
eventType,
451+
},this);
445452
// Make the retainer retain the listener in a WeakMap
446453
weakListeners().map.set(weak,listener);
447454
this.listener=this.callback;
@@ -604,7 +611,7 @@ class EventTarget{
604611
if(root===undefined){
605612
root={size: 1,next: undefined,resistStopPropagation: Boolean(resistStopPropagation)};
606613
// This is the first handler in our linked list.
607-
newListener(root,listener,once,capture,passive,
614+
newListener(this,type,root,listener,once,capture,passive,
608615
isNodeStyleListener,weak,resistStopPropagation);
609616
this[kNewListener](
610617
root.size,
@@ -631,7 +638,7 @@ class EventTarget{
631638
return;
632639
}
633640

634-
newListener(previous,listener,once,capture,passive,
641+
newListener(this,type,previous,listener,once,capture,passive,
635642
isNodeStyleListener,weak,resistStopPropagation);
636643
root.size++;
637644
root.resistStopPropagation||=Boolean(resistStopPropagation);
@@ -674,6 +681,28 @@ class EventTarget{
674681
}
675682
}
676683

684+
[kRemoveWeakListenerHelper](type,listener){
685+
constroot=this[kEvents].get(type);
686+
if(root===undefined||root.next===undefined)
687+
return;
688+
689+
constcapture=listener.capture===true;
690+
691+
lethandler=root.next;
692+
while(handler!==undefined){
693+
if(handler===listener){
694+
handler.remove();
695+
root.size--;
696+
if(root.size===0)
697+
this[kEvents].delete(type);
698+
// Undefined is passed as the listener as the listener was GCed
699+
this[kRemoveListener](root.size,type,undefined,capture);
700+
break;
701+
}
702+
handler=handler.next;
703+
}
704+
}
705+
677706
/**
678707
* @param{Event} event
679708
*/

‎test/parallel/test-eventtarget-memoryleakwarning.js‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
// Flags: --no-warnings
1+
// Flags: --expose-internals --no-warnings --expose-gc
22
'use strict';
33
constcommon=require('../common');
44
const{
55
setMaxListeners,
66
EventEmitter,
77
}=require('events');
88
constassert=require('assert');
9+
const{ kWeakHandler }=require('internal/event_target');
10+
const{ setTimeout }=require('timers/promises');
911

1012
common.expectWarning({
1113
MaxListenersExceededWarning: [
@@ -73,3 +75,20 @@ common.expectWarning({
7375
setMaxListeners(2,ee);
7476
assert.strictEqual(ee.getMaxListeners(),2);
7577
}
78+
79+
{
80+
(async()=>{
81+
// Test that EventTarget listener don't emit MaxListenersExceededWarning for weak listeners that GCed
82+
constet=newEventTarget();
83+
setMaxListeners(2,et);
84+
85+
for(leti=0;i<=3;i++){
86+
et.addEventListener('foo',()=>{},{
87+
[kWeakHandler]: {},
88+
});
89+
90+
awaitsetTimeout(0);
91+
global.gc();
92+
}
93+
})().then(common.mustCall(),common.mustNotCall());
94+
}

‎test/parallel/test-eventtarget.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const{
1616

1717
const{ once }=require('events');
1818

19-
const{promisify,inspect }=require('util');
20-
constdelay=promisify(setTimeout);
19+
const{ inspect }=require('util');
20+
const{setTimeout: delay}=require('timers/promises');
2121

2222
// The globals are defined.
2323
ok(Event);

0 commit comments

Comments
(0)