-
Notifications
You must be signed in to change notification settings - Fork 250
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
feat(child process): Make all child processes silent #1039
Conversation
…was not working, instead hope for the best
This is ready to be merged IMHO |
Don't reinvent the wheel
7ff3098
to
cb63eab
Compare
@simondel @JoshuaKGoldberg With this PR we right now log this if a process runs out-of-memory:
I'm thinking of changing that. If a process crashes and |
Nice! And what if Stryker itself runs out of memory? |
If Stryker itself runs out of memory we cannot do anything to intercept right? As we're out of memory (non-interceptable) |
Good point! |
…ent out of memory
I've implemented the improved error message. I also added an integration test for it (it turns out that creating a memory leak is easy as pie :)). Ready for review |
@@ -97,12 +135,93 @@ export default class ChildProcessProxy<T> { | |||
}); | |||
} | |||
|
|||
private listenToStdoutAndStderr() { | |||
const traceEnabled = this.log.isTraceEnabled(); |
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.
Will this value ever change during execution? If not, I would suggest just reading the value on the line if (traceEnabled)
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.
Good point. Will change that
type MethodPromised = { (...args: any[]): Promise<any> }; | ||
|
||
export type Promisified<T> = { | ||
[K in keyof T]: T[K] extends MethodPromised ? T[K] : T[K] extends Function ? MethodPromised : () => Promise<T[K]>; |
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.
In what case would you cant to use T[K] extends MethodPromised ? T[K]
? Can't you just always call back on T[K] extends Function ? MethodPromised
?
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.
Not sure what you are getting at. There are 3 scenarios supported here. In case the method already returns a promise, it is left alone. In case the method returns something else, it is changed to returning a promise. In case it's not a method, it is mapped to a method returning a promise of that type. This is not perfect (still missing the type arguments in some cases), but is the best I could came up with.
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.
Ah I see what you mean. You mean change it to this:
export type Promisified<T> = {
[K in keyof T]: T[K] extends Function ? MethodPromised : () => Promise<T[K]>;
};
The problem is that you don't really want to use MethodPromised
if you don't have to. It doesn't help you with method types, converting it to (...args: any[]): Promise<any>
. It would be awesome if we could keep the method signature, but I don't know how. I've created a stack overflow question for this:
I would say: let's keep it like this for now.
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.
Wow! We have an answer on stack overflow! Fast. It seems that it is possible using infer
. The problem now is that we do not have support for rest type parameters yet. So needs to be implemented using the death by a thousand overloads syntax. Let's keep it like it is for now and improve once TS 3 is out.
// TODO: Implement using unknown and rest parameter type
// https://blogs.msdn.microsoft.com/typescript/2018/07/12/announcing-typescript-3-0-rc/#tuples-and-parameters
interface Func1<R> {
(): R;
}
interface Func2<T1, R> {
(arg: T1): R;
}
interface Func3<T1, T2, R> {
(arg: T1, arg2: T2): R;
}
interface Func<R> {
(...args: any[]): R;
}
interface PromisedFunc1<R> {
(): Promise<R>;
}
interface PromisedFunc2<T1, R> {
(arg: T1): Promise<R>;
}
interface PromisedFunc3<T1, T2, R> {
(arg: T1, arg2: T2): Promise<R>;
}
interface PromisedFunc<R> {
(...args: any[]): Promise<R>;
}
export type Promisified<T> = {
[K in keyof T]: T[K] extends { (...args: any[]): Promise<any> } ? T[K] :
T[K] extends Func3<infer T1, infer T2, infer R> ? PromisedFunc3<T1, T2, R> :
T[K] extends Func2<infer T1, infer R> ? PromisedFunc2<T1, R> :
T[K] extends Func1<infer R> ? PromisedFunc1<R> :
T[K] extends Func<infer R> ? PromisedFunc<R> :
() => Promise<T[K]>;
};
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.
As TS 3 is out since a couple of hours, I've implemented this in the latest commit. Awesome!
// This is important! Be sure to bind to `this` | ||
this.handleUnexpectedExit = this.handleUnexpectedExit.bind(this); | ||
this.handleError = this.handleError.bind(this); | ||
this.worker.on('exit', this.handleUnexpectedExit); |
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.
Can we save the exit
and error
worker messages on the worker type itself so we don't have to use magic strings? Or are they a part of NodeJS?
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? The worker
here is an instance of ChildProcess
right?
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.
Do you mean NodeJS's ChildPRocess
? Do we choose to name the event 'exit'
or does NodeJS determine this name? If NodeJS determines it, this code is fine. If we personally have code that raises an event with name 'exit'
then we should use a constant.
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.
Yes, it's a node thing. It works based on events. Not too great if you ask me, but that's how it works.
const result = await this.underlyingTestRunner.run(options); | ||
// If the test runner didn't report on coverage, let's try to do it ourselves. | ||
if (!result.coverage) { | ||
result.coverage = (Function('return this'))().__coverage__; |
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.
How does this work?
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.
It's the same as used by istanbul. It's a way to always grab the global object. I could change it to global.__coverage__
. that should be fine. Agreed?
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.
Sounds good!
@simondel Thanks for the review. I've refactored some code and commented on the rest. Could you take another look? |
…to determine if it ran out of memory.
Make all test runner and transpiler child processes silent. The standard out and standard error (stdout and stderr) are now only visible when
loglevel: 'trace'
. If a child process crashes, the last 10 messages received are logged as warning.This is also a refactoring of the way we spawn child processes. Instead of having 2 similar implementations (one for transpiler and one for test runners), they are both consolidated in one coherent
ChildProcessProxy
abstraction.Also clean up the test runner decorator pattern. Timeouts and retries are now implemented only once. Recognizing that the child process crashed is done by validating that the error is an instance of
ChildProcessCrashedError
. No process specifics other than the name of the error is known from the outside.The
Task
class is also refactored. Instead of relying on a custom implementation, it uses thePromise.race
method for timeout functionality.Fixes #1038 #976