From bccaa678002e0e72161dd87340bab003a4425834 Mon Sep 17 00:00:00 2001 From: Guru Prasad Srinivasa Date: Fri, 12 Aug 2022 22:31:31 -0400 Subject: [PATCH 1/4] Fixes memory-leaks --- index.js | 71 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 1612c7d..9ca734e 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,7 @@ function assertListener(listener) { function getListeners(instance, eventName) { const events = eventsMap.get(instance); if (!events.has(eventName)) { - events.set(eventName, new Set()); + return null; } return events.get(eventName); @@ -38,7 +38,7 @@ function getEventProducers(instance, eventName) { const key = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer; const producers = producersMap.get(instance); if (!producers.has(key)) { - producers.set(key, new Set()); + return null; } return producers.get(key); @@ -79,7 +79,14 @@ function iterator(instance, eventNames) { }; for (const eventName of eventNames) { - getEventProducers(instance, eventName).add(producer); + let set = getEventProducers(instance, eventName); + if (!set) { + set = new Set(); + const producers = producersMap.get(instance); + producers.set(eventName, set); + } + + set.add(producer); } return { @@ -111,7 +118,14 @@ function iterator(instance, eventNames) { queue = undefined; for (const eventName of eventNames) { - getEventProducers(instance, eventName).delete(producer); + const set = getEventProducers(instance, eventName); + if (set) { + set.delete(producer); + if (set.size === 0) { + const producers = producersMap.get(instance); + producers.delete(eventName); + } + } } flush(); @@ -221,6 +235,9 @@ class Emittery { anyMap.set(this, new Set()); eventsMap.set(this, new Map()); producersMap.set(this, new Map()); + + producersMap.get(this).set(anyProducer, new Set()); + this.debug = options.debug || {}; if (this.debug.enabled === undefined) { @@ -259,7 +276,14 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { assertEventName(eventName); - getListeners(this, eventName).add(listener); + let set = getListeners(this, eventName); + if (!set) { + set = new Set(); + const events = eventsMap.get(this); + events.set(eventName, set); + } + + set.add(listener); this.logIfDebugEnabled('subscribe', eventName, undefined); @@ -277,7 +301,14 @@ class Emittery { eventNames = Array.isArray(eventNames) ? eventNames : [eventNames]; for (const eventName of eventNames) { assertEventName(eventName); - getListeners(this, eventName).delete(listener); + const set = getListeners(this, eventName); + if (set) { + set.delete(listener); + if (set.size === 0) { + const events = eventsMap.get(this); + events.delete(eventName); + } + } this.logIfDebugEnabled('unsubscribe', eventName, undefined); @@ -321,7 +352,7 @@ class Emittery { enqueueProducers(this, eventName, eventData); - const listeners = getListeners(this, eventName); + const listeners = getListeners(this, eventName) || new Set(); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners]; @@ -350,7 +381,7 @@ class Emittery { this.logIfDebugEnabled('emitSerial', eventName, eventData); - const listeners = getListeners(this, eventName); + const listeners = getListeners(this, eventName) || new Set(); const anyListeners = anyMap.get(this); const staticListeners = [...listeners]; const staticAnyListeners = [...anyListeners]; @@ -401,28 +432,34 @@ class Emittery { this.logIfDebugEnabled('clear', eventName, undefined); if (typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number') { - getListeners(this, eventName).clear(); + const set = getListeners(this, eventName); + if (set) { + set.clear(); + } const producers = getEventProducers(this, eventName); + if (producers) { + for (const producer of producers) { + producer.finish(); + } - for (const producer of producers) { - producer.finish(); + producers.clear(); } - - producers.clear(); } else { anyMap.get(this).clear(); - for (const listeners of eventsMap.get(this).values()) { + for (const [eventName, listeners] of eventsMap.get(this).entries()) { listeners.clear(); + eventsMap.get(this).delete(eventName); } - for (const producers of producersMap.get(this).values()) { + for (const [eventName, producers] of producersMap.get(this).entries()) { for (const producer of producers) { producer.finish(); } producers.clear(); + producersMap.get(this).delete(eventName); } } } @@ -434,8 +471,8 @@ class Emittery { for (const eventName of eventNames) { if (typeof eventName === 'string') { - count += anyMap.get(this).size + getListeners(this, eventName).size + - getEventProducers(this, eventName).size + getEventProducers(this).size; + count += anyMap.get(this).size + (getListeners(this, eventName) || new Set()).size + + (getEventProducers(this, eventName) || new Set()).size + (getEventProducers(this) || new Set()).size; continue; } From a36e86581b2f397bd7a19c65e4c4fb7409c82c07 Mon Sep 17 00:00:00 2001 From: Guru Prasad Srinivasa Date: Sat, 13 Aug 2022 09:50:57 -0400 Subject: [PATCH 2/4] Returns undefined instead of null --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 9ca734e..0dae57b 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,7 @@ function assertListener(listener) { function getListeners(instance, eventName) { const events = eventsMap.get(instance); if (!events.has(eventName)) { - return null; + return; } return events.get(eventName); @@ -38,7 +38,7 @@ function getEventProducers(instance, eventName) { const key = typeof eventName === 'string' || typeof eventName === 'symbol' || typeof eventName === 'number' ? eventName : anyProducer; const producers = producersMap.get(instance); if (!producers.has(key)) { - return null; + return; } return producers.get(key); From 0349c3d6338f9bdb01087b2026086ee8fad643a6 Mon Sep 17 00:00:00 2001 From: Guru Prasad Srinivasa Date: Sat, 13 Aug 2022 09:51:22 -0400 Subject: [PATCH 3/4] Adds test to ensure that eventsMap is cleaned up This cleanup is expected to occur when all event listeners for a specific event are removed --- index.js | 5 ++--- maps.js | 9 +++++++++ test/index.js | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 maps.js diff --git a/index.js b/index.js index 0dae57b..b7ed7b6 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,7 @@ 'use strict'; -const anyMap = new WeakMap(); -const eventsMap = new WeakMap(); -const producersMap = new WeakMap(); +const {anyMap, producersMap, eventsMap} = require('./maps.js'); + const anyProducer = Symbol('anyProducer'); const resolvedPromise = Promise.resolve(); diff --git a/maps.js b/maps.js new file mode 100644 index 0000000..7bf3421 --- /dev/null +++ b/maps.js @@ -0,0 +1,9 @@ +const anyMap = new WeakMap(); +const eventsMap = new WeakMap(); +const producersMap = new WeakMap(); + +module.exports = { + anyMap, + eventsMap, + producersMap +}; diff --git a/test/index.js b/test/index.js index 7de7788..3dc4572 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,7 @@ import test from 'ava'; import delay from 'delay'; import pEvent from 'p-event'; import Emittery from '../index.js'; +import {eventsMap} from '../maps.js'; test('on()', async t => { const emitter = new Emittery(); @@ -348,6 +349,19 @@ test('off() - no listener', t => { }, TypeError); }); +test('off() - Clears global maps when all listeners are removed', t => { + const emitter = new Emittery(); + + const event = 'string'; + const cb = () => {}; + + emitter.on(event, cb); + t.true(eventsMap.get(emitter).get(event).size === 1); + + emitter.off(event, cb); + t.true(eventsMap.get(emitter).get(event) === undefined); +}); + test('once()', async t => { const fixture = '🌈'; const emitter = new Emittery(); From 642a82eb497ede6f252b20b871f7a66a73f7fa49 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 25 Aug 2022 10:21:06 +0700 Subject: [PATCH 4/4] Update index.js --- test/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/index.js b/test/index.js index 3dc4572..03bae4a 100644 --- a/test/index.js +++ b/test/index.js @@ -349,17 +349,17 @@ test('off() - no listener', t => { }, TypeError); }); -test('off() - Clears global maps when all listeners are removed', t => { +test('off() - clears global maps when all listeners are removed', t => { const emitter = new Emittery(); const event = 'string'; - const cb = () => {}; + const callback = () => {}; - emitter.on(event, cb); - t.true(eventsMap.get(emitter).get(event).size === 1); + emitter.on(event, callback); + t.is(eventsMap.get(emitter).get(event).size, 1); - emitter.off(event, cb); - t.true(eventsMap.get(emitter).get(event) === undefined); + emitter.off(event, callback); + t.is(eventsMap.get(emitter).get(event), undefined); }); test('once()', async t => {