-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
events: remove listener after timer is finished #35992
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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 <jasnell@gmail.com>
Date: Fri, 6 Nov 2020 07:07:43 -0800
Subject: [PATCH] timers: cleanup abort listener on awaitable timers
Signed-off-by: James M Snell <jasnell@gmail.com>
---
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