-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
[jest-worker] Detect infinite loops #10701
Conversation
Alright, thanks for the work so far, it looks promising, let me try to respond to some things :)
I agree with that
Hmm so I think this could be added to the existing tests of BTW, do you expect any difficulties with the Node thread workers or can they support the inspector in the same way that child process workers do? |
packages/jest-haste-map/src/index.ts
Outdated
if (inspectorUrl !== undefined) { | ||
session = new inspector.Session(); | ||
await session.connect(); | ||
await session.post('Debugger.enable'); |
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.
also doesn't return a promise, but you can promisify post
(just left some nits on the current code, feel free to ignore while you figure out the approach to take 🙂) |
@jeysal @SimenB Thanks for your review 👍 I moved the inspector initialization inside I keep testing the Tell me if I am wrong but for me the |
I suppose there are multiple ways to go about this - either the child always sends heartbeats from when it is spawned until it is destroyed, or the child only sends heartbeats while working on a task. I suspect the former is easier, and the overhead of an interval sending one small message usually every couple of seconds seems negligible. So in that case the interval would be scheduled in initialize and cleared in |
You are right, it seems easier to handle ! |
ceae148
to
328bdae
Compare
packages/jest-worker/src/index.ts
Outdated
@@ -72,18 +74,23 @@ export default class JestWorker { | |||
private _farm: Farm; | |||
private _options: FarmOptions; | |||
private _workerPool: WorkerPoolInterface; | |||
private _inspector: Session | undefined; |
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.
private _inspector: Session | undefined; | |
private _inspectorSession: Session | undefined; |
easier to grok when we have the inspector
import
@@ -59,6 +66,12 @@ const messageListener: NodeJS.MessageListener = request => { | |||
}; | |||
process.on('message', messageListener); | |||
|
|||
function sendParentMessageHeartbeat() { | |||
if (process && process.send) { |
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 (process && process.send) { | |
if (process?.send) { |
when is process
not defined?
@@ -42,6 +45,10 @@ const messageListener = (request: any) => { | |||
const init: ChildMessageInitialize = request; | |||
file = init[2]; | |||
setupArgs = request[3]; | |||
heartbeatIntervalValue = request[4]; |
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.
same comments as for process
again, feel free to ignore my nits, I was just stuck in line at the grocery store with nothing better to do 😀 |
I try to test the current implementation with a simple test file containing an infinite loop to see how the workers and the child processes handle it: // infinite_loop.test.ts
it ('should handle infinite loop') {
while (true) {}
} But unfortunately when I try to test if the child processes emit correctly the I don’t know if the logs are stopped when the child processes are working or if they don’t emit anything at all. (Maybe they get stopped before the first interval ends.) |
packages/jest-worker/src/index.ts
Outdated
if (inspectorUrl !== undefined) { | ||
session = new inspector.Session(); | ||
session.connect(); | ||
session.post('Debugger.enable'); |
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.
this is async
session.post('Debugger.enable'); | |
await util.promisfy(session.post)('Debugger.enable'); |
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.
TS is complaining about the return type of promisify
:
Argument of type '"Debugger.enable"' is not assignable to parameter of type '"NodeWorker.disable"'.
It returns something with this type:
// util.promisify(session.post)('Debugger.enable')
function util.promisify<"NodeWorker.disable", unknown>(fn: (arg1: "NodeWorker.disable", callback: (err: Error | null, result: unknown) => void) => void): (arg1: "NodeWorker.disable") => Promise<unknown> (+13 overloads)
Also, what is the best way to handle the asynchronous assignment of the inspector inside constructor
?
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 right, I had issues with promisify myself #8596 (comment)
Manual new Promise
should work 👍
Regarding best way of async init is probably a setup function of some sort... Unless we can intercept the user's function calls as they are all async?
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.
Or exposing a factory function instead of the class directly. Can do that in Jest 27 even if this PR doesn't land in time for it
}); | ||
} | ||
// @ts-expect-error: adding custom properties to errors. | ||
error.type = 1; |
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.
1
? should we add a name
?
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.
Should we add another PARENT_MESSAGE_ERROR
?
// types.ts
export const PARENT_MESSAGE_HEARTBEAT_ERROR: 5 = 5;
export type PARENT_MESSAGE_ERROR =
...
| typeof PARENT_MESSAGE_HEARTBEAT_ERROR;
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.
That makes sense to 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.
Is this actually worth it though? It's not a message that is actually ever sent is it? It's just to set a property on an error that can further identify the error but that I don't think we really need?
I think declaring messages that are not actually messages ever being passed between parent and child is confusing
cba2c2f
to
603769f
Compare
@ayshiff isn't the problem you described already integration testing level? I would imagine the first tests for |
@jeysal I come to ask for help if you have time 👍 Inside the tests of jest.mock('jest-worker', () => ({
create: jest.fn(worker => {
mockWorker = jest.fn((...args) => require(worker).worker(...args));
mockEnd = jest.fn();
return {
end: mockEnd,
worker: mockWorker,
};
}),
})); But in the test |
870d5c5
to
aa595aa
Compare
Hmm that's odd, I thought maybe it would have to be |
@jeysal My test changes are already pushed inside |
Haven't taken a look yet, but #9054 just randomly popped into my inbox because someone commented, perhaps related? |
Nope, #9054 was a false report |
@ayshiff your |
@jeysal Now that I have been able to test that the behavior between process childs and workers was consistent (heartbeat logic inside
I suspect that some Also, I tried to test if the inspector was properly initialized when we call the I imagine that the inspector is not properly closed between tests, but i can't find where.
Thank you again for taking time to help me 👍 |
I imagine this could be as straightforward as creating a test that does
I suppose we would do something similar to when receiving a CLIENT_ERROR, that is call Regarding the test timeouts: Could it be that some tests really just block that long? We have a very high test timeout set, and also consider that the e2e tests are basically doing |
ec57961
to
0c8f158
Compare
Rebased and pushed some minor cleanup. |
Thanks for your help 👍 |
I'm not very familiar with |
Oh yes you are right. I'll ping you when I'm done with it 👍 |
This PR is stale because it has been open 1 year with no activity. Remove stale label or comment or this will be closed in 30 days. |
This PR was closed because it has been stalled for 30 days with no activity. Please open a new PR if the issue is still relevant, linking to this one. |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Closes #9785.
As said with @jeysal, here is the Draft PR to show where I am.
As I said here, I have been stuck for a moment on this and I will need some help 👍
I would like to have a TDD oriented approach to be able to make sure that the modifications I make are valid. Can someone give me any hints where to start to test the jest-worker heartbeat behavior as this is the part I'm having a hard time moving forward on ?
Currently the inspector initialization is done in
jest-haste-map
but I think it should be done injest-worker
or injest-runner
.I made a graph to show the logic i tried to set up.
Test plan