mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
repl: keep reference count for process.on('newListener')
When investigating a memory leak in one of our applications, we discovered that this listener holds on to a `REPLServer` instance and all heap objects transitively kept alive by it by capturing as part of its closure. It's cleaner to declare the listener outside of the `REPLServer` class and to actually clean it up properly when it is no longer required or meaningful, which is easily achieved through keeping a reference count. PR-URL: https://github.com/nodejs/node/pull/61895 Backport-PR-URL: https://github.com/nodejs/node/pull/63194 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
1 parent
b943a31a0c
commit
8450c1cf33
2 files changed
+49
-19
No files matched your search
+32
-19
@@ -213,7 +213,35 @@ const kBufferedCommandSymbol = Symbol('bufferedCommand');
|
||||
const kContextId = Symbol('contextId');
|
||||
const kLoadingSymbol = Symbol('loading');
|
||||
|
||||
let addedNewListener = false;
|
||||
function processNewListener(event, listener) {
|
||||
if (event === 'uncaughtException' &&
|
||||
process.domain &&
|
||||
listener.name !== 'domainUncaughtExceptionClear' &&
|
||||
domainSet.has(process.domain)) {
|
||||
// Throw an error so that the event will not be added and the current
|
||||
// domain takes over. That way the user is notified about the error
|
||||
// and the current code evaluation is stopped, just as any other code
|
||||
// that contains an error.
|
||||
throw new ERR_INVALID_REPL_INPUT(
|
||||
'Listeners for `uncaughtException` cannot be used in the REPL');
|
||||
}
|
||||
}
|
||||
|
||||
let processNewListenerUseCount = 0;
|
||||
function addProcessNewListener() {
|
||||
if (processNewListenerUseCount++ === 0) {
|
||||
// Add this listener only once and use a WeakSet that contains the REPLs
|
||||
// domains. Otherwise we'd have to add a single listener to each REPL
|
||||
// instance and that could trigger the `MaxListenersExceededWarning`.
|
||||
process.prependListener('newListener', processNewListener);
|
||||
}
|
||||
}
|
||||
|
||||
function removeProcessNewListener() {
|
||||
if (--processNewListenerUseCount === 0) {
|
||||
process.removeListener('newListener', processNewListener);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Hack for require.resolve("./relative") to work properly.
|
||||
@@ -370,24 +398,9 @@ function REPLServer(prompt,
|
||||
// It is possible to introspect the running REPL accessing this variable
|
||||
// from inside the REPL. This is useful for anyone working on the REPL.
|
||||
module.exports.repl = this;
|
||||
} else if (!addedNewListener) {
|
||||
// Add this listener only once and use a WeakSet that contains the REPLs
|
||||
// domains. Otherwise we'd have to add a single listener to each REPL
|
||||
// instance and that could trigger the `MaxListenersExceededWarning`.
|
||||
process.prependListener('newListener', (event, listener) => {
|
||||
if (event === 'uncaughtException' &&
|
||||
process.domain &&
|
||||
listener.name !== 'domainUncaughtExceptionClear' &&
|
||||
domainSet.has(process.domain)) {
|
||||
// Throw an error so that the event will not be added and the current
|
||||
// domain takes over. That way the user is notified about the error
|
||||
// and the current code evaluation is stopped, just as any other code
|
||||
// that contains an error.
|
||||
throw new ERR_INVALID_REPL_INPUT(
|
||||
'Listeners for `uncaughtException` cannot be used in the REPL');
|
||||
}
|
||||
});
|
||||
addedNewListener = true;
|
||||
} else {
|
||||
addProcessNewListener();
|
||||
this.once('exit', removeProcessNewListener);
|
||||
}
|
||||
|
||||
domainSet.add(this._domain);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const { startNewREPLServer } = require('../common/repl');
|
||||
const assert = require('assert');
|
||||
|
||||
const originalProcessNewListenerCount = process.listenerCount('newListener');
|
||||
const { replServer } = startNewREPLServer();
|
||||
|
||||
const listenerCountBeforeClose = process.listenerCount('newListener');
|
||||
replServer.close();
|
||||
replServer.once('exit', common.mustCall(() => {
|
||||
setImmediate(common.mustCall(() => {
|
||||
const listenerCountAfterClose = process.listenerCount('newListener');
|
||||
assert.strictEqual(listenerCountAfterClose, listenerCountBeforeClose - 1);
|
||||
assert.strictEqual(listenerCountAfterClose, originalProcessNewListenerCount);
|
||||
}));
|
||||
}));
|
||||
Reference in new issue
Block a user