-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
test_runner: cleanup test timeout abort listener #48915
test_runner: cleanup test timeout abort listener #48915
Conversation
Review requested:
|
lib/internal/test_runner/test.js
Outdated
deferred.promise = PromisePrototypeThen(deferred.promise, () => { | ||
throw new ERR_TEST_FAILURE( | ||
`test timed out after ${timeout}ms`, | ||
kTestTimeoutFailure, | ||
); | ||
}); |
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 wanted to put this in the setTimeout
but for some reason, the test-runner-output.mjs
test failed for the describe_it
file with some missing stack trace
lib/internal/test_runner/test.js
Outdated
function throwOnAbort() { | ||
deferred.reject(new AbortError(undefined, { cause: signal.reason })); | ||
clearTimeout(timer); | ||
} |
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.
Done this to match the current behavior:
Line 48 in 78f6952
reject(new AbortError(undefined, { cause: signal?.reason })); |
but we should align it with just using an abort signal with no timeout - no error being thrown or throw an abort error
lib/internal/test_runner/test.js
Outdated
const cleanAbort = addAbortListener(signal, throwOnAbort)[SymbolDispose]; | ||
const timer = setTimeout(() => { | ||
deferred.resolve(); | ||
cleanAbort(); | ||
}, timeout); | ||
|
||
timer.unref(); |
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.
Could/Should this be an AbortSignal.any([signal, AbortSignal.timeout(timeout)])
etc?
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 don't think it would change anything as the timer won't get cleaned...
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.
What do you mean about the timer not getting clean?
The AbortSignal.any
uses weak refs, so once this is GCed it would clear the timeout...
I think this suggestion could potentially reduce the complexity here, and also make it easier to land on v18, which doesn't have dispose & addAbortListener
at the moment
WDYT?
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.
thank you! this is a much cleaner solution! the only problem here is that we create 2 new AbortSignal (AbortSignal.any
and AbortSignal.timeout
) for every describe/test/hook run, which can be bad for performance...
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.
@MoLow what do you think? should we remove the addAbortListener and dispose in order to support v18 and v16?
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 don't think this actually simplifies 😬
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.
@rluvaton I had something like this in mind:
function stopTest(timeout, signal) {
const timeoutSignal = timeout !== kDefaultTimeout && AbortSignal.timeout(timeout);
const composedSignal = AbortSignal.any([signal, timeoutSignal].filter(Boolean));
return PromisePrototypeThen(once(composedSignal, 'abort'), (e) => {
if (e.target.reason !== timeoutSignal.reason) {
return;
}
throw new ERR_TEST_FAILURE(
`test timed out after ${timeout}ms`,
kTestTimeoutFailure,
);
})
};
But current solution works for me 🙂
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.
If we accept the extra creation of an abort signal I highly prefer that
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 assume we would want to profile both and decide based on that?
@MoLow @benjamingr what is your opinion here?
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've created a benchmark PR and after it is merged we can check different solutions:
lib/internal/test_runner/test.js
Outdated
const cleanAbort = addAbortListener(signal, throwOnAbort)[SymbolDispose]; | ||
const timer = setTimeout(() => { | ||
deferred.resolve(); | ||
cleanAbort(); | ||
}, timeout); | ||
|
||
timer.unref(); |
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.
What do you mean about the timer not getting clean?
The AbortSignal.any
uses weak refs, so once this is GCed it would clear the timeout...
I think this suggestion could potentially reduce the complexity here, and also make it easier to land on v18, which doesn't have dispose & addAbortListener
at the moment
WDYT?
@MoLow updated with your changes and modified to use |
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 don't like the promise+disposer in one object code pattern here + I don't like the explicit promise construction here.
We're core, we can "cheat", we can add the listener with [kWeakHandler]: test
and it will cleanup the listener when the test is done.
to which listener? the abort listener? because using the |
The issue is the listener won't be removed right? kWeakListener means "remove the listener when that resource is gcd" |
(I removed the timeout support so I try to make it work for the simple case) I understand, but when I tried changing the const { aborted } = require('internal/abort_controller');
function stopTest(timeout, signal) {
return aborted(signal, {});
} The test I wrote: const { beforeEach, afterEach, test} = require("node:test");
beforeEach(() => {
global.gc();
});
afterEach(() => {
global.gc();
});
for (let i = 1; i <= 11; ++i) {
test(`${i}`, () => {
global.gc();
});
} it still log that:
also when doing this it had the same effect: function stopTest(timeout, signal) {
return new Promise((resolve) => {
signal.addEventListener('abort', resolve, { [kWeakHandler]: {} });
setImmediate(() => {
global.gc()
}).unref()
});
} Maybe I'm missing something? |
@rluvaton if we want to keep using the original signal we could keep updating |
The problem is that we are not cleaning up the abort listener (see that it still happening even when we GC manually) |
The reason why the warning still printed was because of #48951 |
no longer needed after nodejs#48915 fix PR-URL: nodejs#48989 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Due to fact, #48877 didn't land cleanly on v20.x-staging. This PR somehow, depends on it. So we'll need a manual backport. |
fix nodejs#48475 PR-URL: nodejs#48915 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
no longer needed after nodejs#48915 fix PR-URL: nodejs#48989 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
fix nodejs#48475 PR-URL: nodejs#48915 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
no longer needed after nodejs#48915 fix PR-URL: nodejs#48989 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: nodejs#48915
There is an `if` statement that likely should have been an `else` in the original PR. Refs: #48915 PR-URL: #49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: nodejs#48915 PR-URL: nodejs#49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: #48915 PR-URL: #49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: #48915 PR-URL: #49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: nodejs#48915 PR-URL: nodejs#49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
fix #48475 PR-URL: nodejs/node#48915 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: nodejs/node#48915 PR-URL: nodejs/node#49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
fix #48475 PR-URL: nodejs/node#48915 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
There is an `if` statement that likely should have been an `else` in the original PR. Refs: nodejs/node#48915 PR-URL: nodejs/node#49943 Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Raz Luvaton <rluvaton@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
fix #48475