-
-
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
exit workers gracefully #8206
exit workers gracefully #8206
Conversation
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 like this 🙂
00011e2
to
04b13a7
Compare
23e0434
to
dec56d4
Compare
The diff is getting quite large. |
dec56d4
to
6d5951d
Compare
Alright, this is ready now 👍I'll request reviews from a couple more people than usual since it's a "low level" change, although it's not very invasive. |
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.
fantastic stuff!
const cleanup = async () => { | ||
const {forceExited} = await worker.end(); | ||
if (forceExited) { | ||
console.log( |
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.
stderr instead? not sure
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.
Dunno, last time I added a warning was jest-circus/jest-jasmine2
which also used console.log(chalk.yellow())
. You know the code base better than I do, what is more commonly used for warnings? 😄
@@ -85,15 +90,33 @@ export default class BaseWorkerPool { | |||
throw Error('Missing method createWorker in WorkerPool'); | |||
} | |||
|
|||
end(): void { | |||
async end(): Promise<PoolExitResult> { |
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'm not sure if this should be considered a breaking change or not (thus should only land in 25).
(I know I asked you to make it async, but changing the return type to be a promise, async fn or not, is a change in signature)
25 shouldn't be far out, thoughts on holding off?
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.
TBH I'm not sure about the change from throw to reject you asked me to make.
The argument was that it's annoying if a function can both throw and reject.
I think that this function should only ever throw, never reject. It has the case of invalid usage (end()
called multiple times), but it should never "fail to end" the pool. If there's no other way, SIGKILL
everything. It should always be able to clean up.
If we'd revert that back then it's definitely not breaking because the workers were always end
ed asynchronously, there was just no way to find out when that happened (and the signature itself is backward compatible).
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.
@SimenB would you be fine with changing this back? I think calling this with invalid preconditions should throw immediately and not worst case even result in an uncaught rejection
Thanks for the reviews, I'll try to get to it asap :) |
a730535
to
3dd49d3
Compare
Incorporated feedback. Open points I've commented on:
|
3f56b4b
to
799fda6
Compare
@SimenB I think this should go into the (probably last) 24 minor. I don't want to overload the 25 major with large refactoring changes. |
I have encountered Is there a way to tell |
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
This PR makes child process workers avoid
process.exit
, instead gracefully exiting them by clearing open handles. If they fail to exit gracefully (usually because the user code leaks) after a delay, they are force exited usingSIGTERM
and if that doesn't help after another delay usingSIGKILL
. For Node worker threads, the force exit method isterminate()
.The primary use for this (besides being cleaner) is that we can at least print a warning if tests leak open handles (which this PR also does) instead of effectively just cancelling the whole event loop by terminating the worker. Many users have complained about bugs like
console.log
s being lost and this is likely one of the reasons why stuff like that happens, hopefully exiting gracefully can reduce such hard-to-debug behavior a bit.This is not a breaking change because right now
jest-worker
doesn't guarantee either that the worker processes have exited whenend()
returns (and why should the user care about some leftover worker processes, as long as they will eventually exit).See also #7731 (comment)
jest-runner
: warn if a worker had to be force exitedNodeThreadsWorker
(type errors)NodeThreadsWorker
(not done on CI because worker threads cause many other errors)jest-worker/README.md
Test plan
Updated and added some
jest-worker
tests, added an e2e test for the warning and to check that force exit works.Also run the e2e test and a manual Jest execution with worker threads enabled in
jest-runner
and the Node flag.On master, the e2e test "force exits a worker" passes (because master always force exits them using
process.exit
), but "prints a warning" fails.