-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Migrate forking to a pooled worker design #1428 #2011
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
Conversation
1774235 to
64cc378
Compare
|
Hey @ijjk thanks for picking this up! There's a lot of changes here so I may not be able to have a proper look until next weekend. Going by the commit messages it looks like you're really far along already! |
d2088f5 to
aa270a2
Compare
|
@novemberborn okay thanks for the reply! Yeah, the changes started to add up. I'm still sorting out some test cases, and then hopefully it will be good to go. |
|
@lo1tuma thanks for trying out the changes! Yeah, there are still some kinks to work out. I actually just pushed up a change where I removed the use of I think one of the main reasons it was so much faster when you tried it and then it stalled was because the modules are being cached in memory in I briefly tried using Edit: nevermind on the |
90a7ff2 to
d13df63
Compare
novemberborn
left a comment
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 there's a lot of work here @ijjk! I've tried my best in a first-pass review. There's a lot of comments, some probably repetitive as I figure out what's going on.
I actually just pushed up a change where I removed the use of vm2 since I had to use require in the host context which pretty much eliminated the sandboxing.
I'd rather use an existing module for sandboxing than have to maintain one ourselves. What do you mean by having to use "`require" in the host context"?
You can use the So for example, in const { JSDOM } = require('jsdom')
const { window, document } = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`)
global.window = window
global.document = document
global.fetch = require('node-fetch')I couldn't find any existing modules that handle this.
This is why I started working out a bare minimum custom implementation of |
|
I see. What do you think of https://nodejs.org/api/worker_threads.html? That should solve the sandboxing and the performance issues. I still see a need for properly forked processes, so the pool switching will be necessary. Perhaps we could support non-sandboxed single process as an opt-in which only works for some use cases. |
implementation, added ForkTestPool which is default with current behavior, and added SharedForkTestPool which uses a pool of forks and distributes tests to them as they become available
b9fd625 to
db3099c
Compare
|
@novemberborn The single process mode and shared fork modes are opt-in currently. I could try out a Edit: Not sure why 1 test fails on the mini reporter and 1 on verbose only on Windows in travis-ci. If you have an idea why I'd appreciate it. |
Yea. It no longer requires a flag in the recent Node.js 11 release(s), so it's definitely an option for us. It'll be in Node.js 12 soon enough! |
moved worker options initializing to before test pool run
|
@novemberborn I added the option to use Worker Threads instead of forking. The API was pretty similar to It's also set up so that Worker Threads can be shared in the same way that forks can with the On one project's test suite that has around 632 tests across 59 files, that is using AVA, I found on github, the suite had below run times. Note, by no-cache I mean the cache under
I also ran this on styled-jsx's test-suite which is around 174 tests across 16 files and got below run times.
I didn't run these under a super controlled environment so they might not be perfect estimates but thought it might be helpful to compare on actual projects using AVA. |
107d32d to
a14418f
Compare
in unsupported node version
99cebe9 to
2913fe7
Compare
novemberborn
left a comment
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.
Oops, looks like my comments were never posted. I'll do that now.
Again apologies for the lack of progress on this PR.
api.js
Outdated
| workerOptions.updateSnapshots = true; | ||
| } | ||
|
|
||
| this.Fork = Fork; |
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.
Why expose this?
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 mostly for mocking during testing. I thought it would be easier only mock here instead of each test-pool that needs Fork
lib/cli.js
Outdated
| ranFromCli: true, | ||
| shareForks: conf.shareForks, | ||
| singleProcess: conf.singleProcess, | ||
| workerThreads: conf.workerThreads |
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.
Which combinations do you think should be allowed? I'm tempted to argue it's either --share-forks, or --single-process, *or* --worker-threads.
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.
Yeah that makes sense, combining them can add a lot of complexity
| exit(1); | ||
| }); | ||
|
|
||
| /* istanbul ignore next */ |
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.
Why disable code coverage, here and elsewhere?
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.
Removed all except those related to worker_threads, since I can't test them on unsupported node versions it brings down overall code 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.
I'd have expected codecov to combine coverage reports across all the Node.js versions we test on. This isn't happening?
| const {children} = require.cache[file] || {}; | ||
| if (children) { | ||
| const dependencies = children.map(mod => mod.id); | ||
| statusListener({type: 'dependencies', dependencies}); |
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 looks like a rather different approach, I guess because of caching we can't build as good a dependency map. But children here won't give us a full picture either. Is there a case for not emitting any of these dependencies? It'll affect the watcher so we should document that. The same will go for shared forks.
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 has been updated now that I'm re-using subprocess for SingleProcessTestPool
lib/worker/main.js
Outdated
| const runner = require('./subprocess').getRunner(); | ||
| // If getRunner is missing AVA was probably required | ||
| // directly instead of in a test | ||
| if (typeof global.getRunner === '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.
We can't introduce globals. You could still rely on the require cache to access a shared runner.
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 updated this to only be used in SingleProcess mode to fix an issue with esm causing the getRunner export not to be set here.
|
@ijjk sorry about the late reply - unfortunately I can't share either of the projects :(, I'm happy to keep testing the branch though. I hope we can get the speedup I first experienced, without any errors. |
a06f467 to
de3c3d6
Compare
|
@SneakyMax I understand. I added an option Note: this is only used in |
de3c3d6 to
fb01ed5
Compare
lib/revert-global.js
Outdated
| const storedKeys = {}; | ||
| const storedCache = Object.assign({}, require.cache); | ||
| const storedExtensions = Object.assign({}, module.constructor._extensions); | ||
| const preventCache = ['bluebird', '../index.js', './worker/main.js']; |
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.
Why is Bluebird being prevented from caching?
I've tried running my tests against this branch, and all of our tests that involve bluebird cancellation are failing: we currently have a 'testBootstrap.js" file the we import before all of our tests which enables bluebird cancellation, but it's not being respected, likely due to this logic 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.
Perhaps .getNewLibraryCopy() would be useful so that the test framework can have its own copy of Bluebird without stepping on the application code?
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 was clearing bluebird there to try to not interfere with the test's usage of bluebird but you might be right that clearing it there ended up interfering anyways. I moved it to only clear initially so hopefully it doesn't conflict 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.
Yup, it looks like that fixed it. All of my cancellation tests are running correctly now.
|
Hi @ijjk, sorry I haven't gotten round to picking this up again. |
make sure sema.release is called on bail
bluebird when sharing process
1e9e946 to
d1689e5
Compare
|
@novemberborn I just updated the branch to resolve conflicts, it should be good to review when you have a chance. |
|
Awesome work @ijjk! Hope this gets reviewed soon. 👍 |
Yes me too 😛 This is the next big PR I'm meaning to look at once we land #1947. Apologies for taking so long. It's really hard to find the time (several uninterrupted hours) to go through PRs with this kind of impact. |
|
Once more, apologies for taking so long in looking at this. The bad news is, this is still a really large PR! But, the good news is that we're now supporting experimental features. See this commit for an example: 2b8ba3a @ijjk if you're still keen, perhaps you could make a PR for just one of the options we discussed here. Preferably not anything that needs sandboxes and cleanups. If we put that behind a feature flag we can ship it as a new, opt-in feature without being a breaking change. We can then build on that feature, one PR at a time. It'll be a lot easier to review multiple smaller changes than one big one. We'll also be free to change the implementation as we learn more. If you're burned out by this experience I totally understand, and again I'm sorry it got to this point. Let us know and perhaps somebody else is interested in picking this up. This PR is still too daunting for me so I'm going to go ahead and close it. But I'd really like to see these new worker modes 😍 |

Here's the start of my implementation for migrating forking to a pooled worker design. My implementation has a
SingleProcessTestPool,SharedForkTestPool, andForkTestPoolwhich share a common interface to try to help abstract away the method of executing.Currently, it picks a:TestPoolto use based on the factors set out on the issueFor <N tests (where N is the number of processors), use SingleProcessTestPool; otherwise, use ForkTestPoolWhen --no-fork or equivalent flag is provided, force SingleProcessTestPoolWhen --inspect or --inspect-brk is specified, force SingleProcessTestPoolIn the thread, the.ForkTestPoolwas described as distributing the tests to the different forks as they become idle, and I thought it might simplify the execution to divide the tests beforehand and then create the forks to run them. Doing it this way allowed me to re-use theSingleProcessTestPoolimplementation inside of the forked processTo achieve the testing inside the single process I updated the import of the. Fixes: #1428 Fixes: #1332runnerto use a map with the test file as the key and the runner as the value. I also, utilizedvmto provide isolation between running the tests in the same threadAfter review by @novemberborn, there are now no breaking changes and use of the new test pools are opt-in. Also, the
SingleProcessTestPooldoes not run inside of a sandbox e.g.vmas to avoid having to maintain a sandboxing library sincevm2does not work for this context. You can opt-in to a test pool with below configs/flagsSingleProcessTestPoolusing the--single-processflag orsingleProcessconfigSharedForkTestPoolusing the--share-forksflag orshareForksconfigYou can also make use of
worker_threadsnow on supported node versions (>= 11.8 or > 10.x with--experimental-workernode flag) using the--worker-threadsflag orworkerThreadsconfigTODO:
--no-forkflag andforkconfig in docsIssueHunt Summary