use separate process to probe if drive is safe to watch#14098
use separate process to probe if drive is safe to watch#14098
Conversation
billti
left a comment
There was a problem hiding this comment.
Thanks! Still looking, but getting initial notes out now.
| } | ||
| const directoryName = process.argv[2]; | ||
| const fs: { watch(directoryName: string, options: any, callback: () => {}): any } = require("fs"); | ||
| fs.watch(directoryName, { recursive: true }, () => ({})).close(); |
There was a problem hiding this comment.
I could think of a number of scenarios where this might be more defensive (e.g. check it exists, check it is a dir, check fs.watch returned a watcher object, etc...), but in all those cases seems the resulting error would result in correct behavior anyway in the listening process. However, might be worth a comment to note that this is deliberately "loose" here.
There was a problem hiding this comment.
After thinking a bit more we should be more restrictive here. Will update
|
|
||
| const sys = <ServerHost>ts.sys; | ||
| // use watchGuard process on Windows when node version is 4 or later | ||
| const useWatchGuard = process.platform === "win32" && parseInt(process.version.charAt(1)) >= 4; |
There was a problem hiding this comment.
In about 18 months when Node.js releases v10, this charAt check is going to break.
There was a problem hiding this comment.
fair enough, I'll update similar code in sys as well
| // UNC path: extract server name | ||
| // //server/location | ||
| // ^ <- from 0 to this position | ||
| const firstSlash = path.indexOf(directorySeparator, 2); |
There was a problem hiding this comment.
Any reason this might ever get a UNC path without the next separator (e.g. just "//someshare")? For safety, might want to check for indexOf returning -1 and return undefined (else below returns an empty string).
There was a problem hiding this comment.
technically it might, will fix
| // use watchGuard process on Windows when node version is 4 or later | ||
| const useWatchGuard = process.platform === "win32" && parseInt(process.version.charAt(1)) >= 4; | ||
| if (useWatchGuard) { | ||
| const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined); |
There was a problem hiding this comment.
If I'm following this logic correctly, then local files on a system that doesn't use drive letters (e.g. Mac/Linux with "/user/bill/documents" etc...) will never get a cache key, and thus will never use the cache, always taking the hit to spawn the guard process. Is that right?
There was a problem hiding this comment.
ah.... but of course. "useWatchGuard" in only true it process.platform == "win32". Don't mind me :-)
There was a problem hiding this comment.
issue is specific to Windows version of libuv and does not exist on other platforms. that is why we use watch guard process only on Windows process.platform === "win32"
| if (logger.hasLevel(LogLevel.verbose)) { | ||
| logger.info(`Starting ${process.execPath} with args ${JSON.stringify(args)}`); | ||
| } | ||
| childProcess.execFileSync(process.execPath, args, { stdio: "ignore", env: { "ELECTRON_RUN_AS_NODE": "1" } }); |
There was a problem hiding this comment.
Seems like it might be worth using the 'timeout' option on execFileSync. Seeing as 'fs.watch' by default will keep the process alive until 'close' has been called on the filewatcher, if the callback that calls 'close' in 'watchGuard' doesn't run for some reason, this would effectively hang tsserver. Seems like 1000ms should be a generous worst-case choice.
There was a problem hiding this comment.
close is not called inside the callback but rather on return value of fs.watch
There was a problem hiding this comment.
Actually, looking at watchGuard.js again, seem like it will always exit. The only risk being if fs.watch itself blocks for some reason, but then that's the same call that's going to run in tsserver anyway, so probably no value in the timeout.
|
|
||
| var serverFile = path.join(builtLocalDirectory, "tsserver.js"); | ||
| compileFile(serverFile, serverSources, [builtLocalDirectory, copyright, cancellationTokenFile, typingsInstallerFile].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { types: ["node"], preserveConstEnums: true }); | ||
| compileFile(serverFile, serverSources, [builtLocalDirectory, copyright, cancellationTokenFile, typingsInstallerFile, watchGuardFile].concat(serverSources), /*prefixes*/ [copyright], /*useBuiltCompiler*/ true, { types: ["node"] }); |
There was a problem hiding this comment.
Was the removal of preserveConstEnums: true deliberate? Is it needed in this change?
| } | ||
| const directoryName = process.argv[2]; | ||
| const fs: { watch(directoryName: string, options: any, callback: () => {}): any } = require("fs"); | ||
| fs.watch(directoryName, { recursive: true }, () => ({})).close(); |
There was a problem hiding this comment.
Does the libuv bug definitely cause the process to crash synchronously on calling "fs.watch"? (i.e. it's not going to get to process.exit(0) before it the crash would occur).
There was a problem hiding this comment.
it is crashes synchronously when creating watcher
| function isNode4OrLater(): boolean { | ||
| return parseInt(process.version.charAt(1)) >= 4; | ||
| } | ||
| const nodeVersion = getNodeMajorVersion(); |
There was a problem hiding this comment.
Cool. parseInt(process.versions.node) would also have done the trick ;-)
There was a problem hiding this comment.
good to know :) can change it to your proposal if you insist
| // //server/location | ||
| // ^ <- from 0 to this position | ||
| const firstSlash = path.indexOf(directorySeparator, 2); | ||
| return firstSlash !== -1 ? path.substring(0, firstSlash).toLowerCase() : undefined; |
| return undefined; | ||
| } | ||
|
|
||
| export function isUNCPath(s: string): boolean { |
| let status = cacheKey && statusCache.get(cacheKey); | ||
| if (status === undefined) { | ||
| if (logger.hasLevel(LogLevel.verbose)) { | ||
| logger.info(`${cacheKey} not found in cache...`); |
There was a problem hiding this comment.
we should log the path as well.
| logger.info(`${cacheKey} not found in cache...`); | ||
| } | ||
| try { | ||
| const args = [combinePaths(__dirname, "watchGuard.js"), path]; |
There was a problem hiding this comment.
make sure LKG has the file name.
use dedicated process to determine if it is safe to watch folders
use dedicated process to determine if it is safe to watch folders
fixes #13997