-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Error object stored on Runnable._trace leaks memory #3119
Comments
@schuay Is this a bug in V8? |
I don't really think Mocha should be changing any code unless there are widespread problems with this. If it's a handful of projects having the problem, they should be able to work around it. |
if it turns out that this is a "feature" and not a "bug" in V8, then we should modify Mocha, and this seems reasonable: this._trace = new Error().stack; then we can just |
I wouldn't necessarily call it a bug on the V8 side. Our expectation is that in general Error objects are not long-lived, that not a huge number of them are live at once, and that for most Error objects the Lazy stack formatting exploits that to skip expensive work. But that leads to the 'leak' in this case since we need to keep a few objects around in order to be able to format the stack later. Your proposed solution above will basically format So I'm not sure there's currently a perfect solution that will be ideal in all use-cases. In V8, we may be able to avoid keeping the closure alive, which might already improve things significantly. Would it be an option to avoid exposing stack traces entirely? |
@schuay Yes, you're right about the slowdown. The point of storing the describe('test suite', function () {
it('is an async test which is bad', function (done) {
setTimeout(() => {
done(); // this test passes
setTimeout(done); // but this is a problem.
});
});
it('is an async test just minding its own business', function (done) {
setTimeout(() => {
// the previous test's error appears somewhere around here,
// even though this test also passes!
done();
});
});
}); IMO, the "real" solution is one of:
Mocha could address this by basically implementing @sokra's hack behind a flag, I guess. |
ugh, and the maybe someone can spend an afternoon and rewrite the test runner as a |
Hmm, I'm not familiar with Mocha at all. Could you elaborate what the problem is in your previous code example? |
@schuay So, you call When that async execution is complete, the user executes the But this doesn't necessarily halt execution of the test. Users can do weird stuff (and they do) like trying to call that Given the example as similar to the above: describe('test suite', function () {
it('is a good async test', function (done) {
setTimeout(done);
});
it('is a good synchronous test', function () {
// omitting the parameter means the test is not expected to be asynchronous
// unless a Promise is returned
});
it('is an async test which is bad', function (done) {
setTimeout(() => {
done(); // this test passes
setTimeout(done); // but this is a problem.
});
});
it('is an async test just minding its own business', function (done) {
setTimeout(() => {
// the previous test's error appears somewhere around here,
// even though this test also passes!
done();
});
});
}); We will see output from Mocha like this (roughly):
Now, where did we call Does that help? |
I see, thanks. If you don't necessarily need the full stack trace, another option would be V8's Using that, you could fetch e.g. the line/column numbers and the file name of the top frame and skip full stack trace formatting. Once the stack is formatted that way, all internal information (e.g. the closure and receiver of each frame) is freed. Of course, ensuring that Error objects go out of scope at some point would be the best fix :) And just FYI, there's also support for async stacks in DevTools / the inspector protocol, although I suppose that won't be relevant for you. |
Mocha doesn't just run in V8, so we can't use runtime-specific APIs.
It is relevant, actually. While it means the user will need to open a separate tool to track down what's going on, at least such a tool now exists. That means it may be OK to simply drop the stack and tell the user to open an inspector to figure it out. 😛 @schuay In what version of V8 was this behavior introduced (specifically, which Node.js version does it correspond to)? |
In theory you could check for the existence of
Hmm, not sure, but it's been roughly one year from looking at |
Latest Node.js supports inspector bindings, so in theory you can enable async stacks using Debugger domain and then somehow fetch this stack trace. |
this._trace.stack = this._trace.stack; This is a workaround without functional changes. It forces v8 to "calculate" the stack trace as string. |
Accessing But as discussed above, eager formatting is probably to slow to be a viable fix. I think |
"Fixing" this bug will cause loss of functionality, but it's only "exceptional" functionality which should not be directly relied on, so I'm calling it |
Could the stack trace be stored only when a command line flag (something like The slowdown of eager formatting would be acceptable because it would only occur when a user was trying to find where done was called twice. Just my two cents :) |
@harrysarson Not a bad idea, but it's also kind of an obscure thing to add a command-line flag for. AFAIK this also affects browser (Chrome) users. I think I'd like to just rip it out for now and see how it goes. |
- this means "multiple calls to done" will once again fail to produce a stack trace in the case that those multiple calls happen in separate tasks. - however, if the `done()` is called with an error, Mocha will emit that error, but also a note about multiple calls - some reformatting, evidently - removed some pointless checks of Mocha's exit code from "multiple done" integration tests Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
- this means "multiple calls to done" will once again fail to produce a stack trace in the case that those multiple calls happen in separate tasks. - however, if the `done()` is called with an error, Mocha will emit that error, but also a note about multiple calls - some reformatting, evidently - removed some pointless checks of Mocha's exit code from "multiple done" integration tests Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
- this means "multiple calls to done" will once again fail to produce a stack trace in the case that those multiple calls happen in separate tasks. - however, if the `done()` is called with an error, Mocha will emit that error, but also a note about multiple calls - some reformatting, evidently - removed some pointless checks of Mocha's exit code from "multiple done" integration tests Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
FYI: In V8, Error objects keep closures alive until the
err.stack
property is accessed, which prevents collection of the closure (and associated objects) until the Error objects die.Mocha creates a long-living Error for each Runnable:
mocha/lib/runnable.js
Line 56 in 2303c66
Would it make sense to either 1. store a string message instead of an Error, or 2. clear Error.stack, or 3. format Error.stack eagerly instead?
See also: https://crbug.com/v8/7142 and https://twitter.com/wSokra/status/935790750188625920.
The text was updated successfully, but these errors were encountered: