-
Notifications
You must be signed in to change notification settings - Fork 30.2k
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 listener after timer is finished #35992
Conversation
I think this should target |
This also needs to apply the same fix to the From 5d7b661306188dfc2c1ca282fcbddbe3ac81cca6 Mon Sep 17 00:00:00 2001
From: James M Snell <[email protected]>
Date: Fri, 6 Nov 2020 07:07:43 -0800
Subject: [PATCH] timers: cleanup abort listener on awaitable timers
Signed-off-by: James M Snell <[email protected]>
---
lib/timers/promises.js | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/lib/timers/promises.js b/lib/timers/promises.js
index ef1e6437d4..da12eb8cd2 100644
--- a/lib/timers/promises.js
+++ b/lib/timers/promises.js
@@ -58,20 +58,24 @@ function setTimeout(after, value, options = {}) {
return PromiseReject(
lazyDOMException('The operation was aborted', 'AbortError'));
}
- return new Promise((resolve, reject) => {
+ let cancelListener = undefined;
+ const ret = new Promise((resolve, reject) => {
const timeout = new Timeout(resolve, after, args, false, true);
if (!ref) timeout.unref();
insert(timeout, timeout._idleTimeout);
if (signal) {
- signal.addEventListener('abort', () => {
+ cancelListener = () => {
if (!timeout._destroyed) {
// eslint-disable-next-line no-undef
clearTimeout(timeout);
reject(lazyDOMException('The operation was aborted', 'AbortError'));
}
- }, { once: true });
+ };
+ signal.addEventListener('abort', cancelListener);
}
});
+ return cancelListener !== undefined ?
+ ret.finally(() => signal.removeEventListener(cancelListener)) : ret;
}
function setImmediate(value, options = {}) {
@@ -107,19 +111,23 @@ function setImmediate(value, options = {}) {
return PromiseReject(
lazyDOMException('The operation was aborted', 'AbortError'));
}
- return new Promise((resolve, reject) => {
+ let cancelListener = undefined;
+ const ret = new Promise((resolve, reject) => {
const immediate = new Immediate(resolve, [value]);
if (!ref) immediate.unref();
if (signal) {
- signal.addEventListener('abort', () => {
+ cancelListener = () => {
if (!immediate._destroyed) {
// eslint-disable-next-line no-undef
clearImmediate(immediate);
reject(lazyDOMException('The operation was aborted', 'AbortError'));
}
- }, { once: true });
+ };
+ signal.addEventListener('abort', cancelListener);
}
});
+ return cancelListener !== undefined ?
+ ret.finally(() => signal.removeEventListener(cancelListener)) : ret;
}
module.exports = {
--
2.17.1 Slightly different approach using finally that I think simplifies things overall. |
@jasnell this is a small fix and I don't want to create needless process - the fix you're proposing looks fine. Feel free to either push another commit directly to this branch, open a new one or whatever you prefer (probably take the test from here? I checked and it fails on master). |
function cleanupSignalHandlerThenResolve(arg) { | ||
signal.removeEventListener('abort', signalHandler, { once: true }); | ||
resolve(arg); | ||
} |
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 would prefer to not allocate a closure here. This adds overhead.
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 think you have to capture the handler in a closure since you need a reference to it to remove it.
Superseded by #36006 |
Fixes the memory leak bug described in #35990 (comment)
It is very important to me to make the point that I believe we need to do #35990 (comment) if we want users not to run into this footfun.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes