don’t use watchman when not watching#1966
Conversation
7f799b3 to
fd34428
Compare
|
The failing check here is also failing on master |
Yeah this looks pretty good, although I would suggest first trying to use Watchman even in the non-watch code-path (it will be much faster on a very large repo) before falling back to a non-Watchman alternative. |
|
I actually found |
|
can we have this as an option? so we can use |
How large are your projects? Watchman was built to support repos with well over a million files in them: in these large scenarios, querying an always-up-to-date, in-memory index that has been prepared ahead of time is always going to be faster than actually |
7f282a4 to
ebd49af
Compare
|
we got this working on Travis by adding: before_install:
# dirty ugly watchman hack https://github.com/facebook/relay/issues/1644#issuecomment-315998313
- if [[ ! -e watchman ]]; then git clone https://github.com/facebook/watchman.git && cd watchman/ && git checkout v4.7.0 && ./autogen.sh && ./configure && make && sudo make install; fi
- sudo sysctl fs.inotify.max_user_watches=524288
- sudo sysctl -p
- cd ~/build/$TRAVIS_REPO_SLUG
# end dirty watchman hack``` |
| describe: 'More verbose logging', | ||
| type: 'boolean', | ||
| }, | ||
| 'noWatchman': { |
There was a problem hiding this comment.
Let's avoid double-negative options. How about just a watchman flag which defaults to true, so --watchman=false to disable?
| client.end(); | ||
| return updateFiles(new Set(), baseDir, filter, resp.files); | ||
| const {baseDir, include, extensions, exclude} = filterOptions; | ||
| if ( |
There was a problem hiding this comment.
Let's refactor this into two functions since this is getting a bit complicated. How about queryFilesWithWatchman and queryFilesWithGlob and you can call them from this condition?
| const client = new RelayWatchmanClient(); | ||
| client.on('error', () => { | ||
| resolve(false); | ||
| }); |
There was a problem hiding this comment.
minor note: this could be syntactically simplified slightly to:
client.on('error', () => resolve(false));
| }); | ||
| client.hasCapability('relative_root') | ||
| .then(() => { | ||
| client.end(); |
There was a problem hiding this comment.
Should we really be creating a client, and then ending it like this? Only to create it again in this success case?
Perhaps a better API would be something like createIfAvailable() which promises to resolve to an instance of RelayWatchmanClient if "available" (has a relative_root) or otherwise returns null, which could then be detected in the code above
|
@leebyron thanks for taking the time to review. I pushed up another commit to address your comments. Please let me know if there is anything else you think I should change. |
| const client = new RelayWatchmanClient(); | ||
| client.on('error', () => resolve(null)); | ||
| client.hasCapability('relative_root') | ||
| .then(() => resolve(client)); |
There was a problem hiding this comment.
Right now this is always resolving to the client, regardless of the return value of relative_root - is that intended? Do you mean for this to be
.then(hasRelativeRoot => hasRelativeRoot ? client : null)| 'noWatchman': { | ||
| describe: 'Do not use watchman when not in watch mode', | ||
| 'watchman': { | ||
| describe: 'Use watchman when not in watch mode', |
There was a problem hiding this comment.
I'm confused. Is watchman still used when not in watch mode?
There was a problem hiding this comment.
Watchman will always be used in watch mode. Watchman will also still be used when not in watch mode unless --watchman=false is passed or we detect watchman is not installed.
There was a problem hiding this comment.
I agree that it's confusing that you could specify that watchman shouldn't be used but also that you want to use watch mode. I think in that scenario it's better to stop and report an error instead of disregarding wishes to not use watchman
| static createIfAvailable(): Promise<?RelayWatchmanClient> { | ||
| return new Promise((resolve) => { | ||
| const client = new RelayWatchmanClient(); | ||
| client.on('error', () => resolve(null)); |
There was a problem hiding this comment.
I'm curious if there are certain kinds of errors which should cause this to be determined as "unavailable" as opposed to considering all errors? I'm interested in what you saw during investigating writing this PR that led you to this
There was a problem hiding this comment.
I based it on the node watchman docs here where they check capabilities for relative_root.
Invoking client.hasCapability when watchman is not installed will cause the on('error') callback to be invoked (or a Unhandled 'error' event to be thrown if not implemented) and never execute the function in the then callback. I'm not able to catch this error by chaining a .catch or rewriting as an async function and wrapping in a try/catch. I'm investigating why that's the case now.
I could check the error in on('error') to make sure it's ENOENT before resolving null.
There was a problem hiding this comment.
@leebyron It seems like fb-watchman does not work as documented in the link above. The only way I found to handle the error from watchman not being installed is through the on('error') callback. I opened an issue on the watchman repo with a minimal example (facebook/watchman#509). How do you think I should proceed?
There was a problem hiding this comment.
Let's try out your current implementation and adjust from there if anything goes wrong. Thanks for opening the issue on watchman, hopefully someone who knows more about it than I do can help us out.
|
@leebyron has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
There was a problem hiding this comment.
See my inline comment, this PR will need a new approach to be mergeable. My suggestion is to revert all changes to RelayCodegenWatcher and RelayCodegenRunner and create only additive changes to RelayCodegenRunner instead and focus the majority of the PR on RelayCompilerBin
Right now RelayCodegenRunner expects a watchmanExpression, however perhaps it could expect that or an array of filenames (via the output of fast-glob, called when --watchman=false is provided). Since that only adds capabilities without removing others, it wouldn't be a breaking change.
| extensions: Array<string>, | ||
| include: Array<string>, | ||
| exclude: Array<string>, | ||
| watchman: boolean, |
There was a problem hiding this comment.
I was working on merging this, but unfortunately this breaking change will prevent me from doing so.
This RelayCodegenRunner is public API, and it's used extensively by Relay projects internally at Facebook which provide complex watchmanExpression that cannot be replicated simply with include/exclude.
| expression: [ | ||
| 'allof', | ||
| config.watchmanExpression, | ||
| RelayCodegenWatcher.buildWatchExpression(config), |
There was a problem hiding this comment.
I think this change here won't be enough - if watchman is disabled, then calling getDirtyWriters will fail here.
|
@leebyron can you take another look? I updated the PR to incorporate your suggestions. There are no longer any breaking changes to any modules. |
|
This is really nice, thanks @robrichard - let me work on merging this |
|
@leebyron has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
Summary: Hey leebyron, I noticed after #1966 was merged the code was amended to create a watchman client solely for purposes of testing if watchman is available. Unfortunately this client is never closed so the process will hang indefinitely. Closes #2010 Reviewed By: alangenfeld Differential Revision: D5548547 Pulled By: leebyron fbshipit-source-id: 69b164558f6121e80d68b9579834adc55264ce55
The unneeded dependency should be fixed by newer Relay: facebook/relay#1966
Fixes #1644
This PR will update relay-compiler to not use watchman unless watching files.