Skip to content

use separate process to probe if drive is safe to watch#14098

Merged
vladima merged 6 commits intomasterfrom
vladima/guardedWatch
Feb 16, 2017
Merged

use separate process to probe if drive is safe to watch#14098
vladima merged 6 commits intomasterfrom
vladima/guardedWatch

Conversation

@vladima
Copy link
Copy Markdown
Contributor

@vladima vladima commented Feb 15, 2017

fixes #13997

Copy link
Copy Markdown
Member

@billti billti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Still looking, but getting initial notes out now.

Comment thread src/server/watchGuard/watchGuard.ts Outdated
}
const directoryName = process.argv[2];
const fs: { watch(directoryName: string, options: any, callback: () => {}): any } = require("fs");
fs.watch(directoryName, { recursive: true }, () => ({})).close();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking a bit more we should be more restrictive here. Will update

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/server.ts Outdated

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In about 18 months when Node.js releases v10, this charAt check is going to break.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough, I'll update similar code in sys as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/server.ts
// UNC path: extract server name
// //server/location
// ^ <- from 0 to this position
const firstSlash = path.indexOf(directorySeparator, 2);
Copy link
Copy Markdown
Member

@billti billti Feb 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically it might, will fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/server.ts
// 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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah.... but of course. "useWatchGuard" in only true it process.platform == "win32". Don't mind me :-)

Copy link
Copy Markdown
Contributor Author

@vladima vladima Feb 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Comment thread src/server/server.ts
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" } });
Copy link
Copy Markdown
Member

@billti billti Feb 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

close is not called inside the callback but rather on return value of fs.watch

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Jakefile.js Outdated

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"] });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the removal of preserveConstEnums: true deliberate? Is it needed in this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, bad merge

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/watchGuard/watchGuard.ts Outdated
}
const directoryName = process.argv[2];
const fs: { watch(directoryName: string, options: any, callback: () => {}): any } = require("fs");
fs.watch(directoryName, { recursive: true }, () => ({})).close();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is crashes synchronously when creating watcher

Comment thread src/compiler/sys.ts
function isNode4OrLater(): boolean {
return parseInt(process.version.charAt(1)) >= 4;
}
const nodeVersion = getNodeMajorVersion();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. parseInt(process.versions.node) would also have done the trick ;-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to know :) can change it to your proposal if you insist

Copy link
Copy Markdown
Member

@billti billti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread src/server/server.ts Outdated
// //server/location
// ^ <- from 0 to this position
const firstSlash = path.indexOf(directorySeparator, 2);
return firstSlash !== -1 ? path.substring(0, firstSlash).toLowerCase() : undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path instead of undefined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/server.ts Outdated
return undefined;
}

export function isUNCPath(s: string): boolean {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for export

Comment thread src/server/server.ts Outdated
let status = cacheKey && statusCache.get(cacheKey);
if (status === undefined) {
if (logger.hasLevel(LogLevel.verbose)) {
logger.info(`${cacheKey} not found in cache...`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should log the path as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Comment thread src/server/server.ts
logger.info(`${cacheKey} not found in cache...`);
}
try {
const args = [combinePaths(__dirname, "watchGuard.js"), path];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure LKG has the file name.

@vladima vladima merged commit 8c54bba into master Feb 16, 2017
@vladima vladima deleted the vladima/guardedWatch branch February 16, 2017 18:32
vladima added a commit that referenced this pull request Feb 16, 2017
use dedicated process to determine if it is safe to watch folders
vladima added a commit that referenced this pull request Feb 16, 2017
use dedicated process to determine if it is safe to watch folders
vladima added a commit that referenced this pull request Feb 16, 2017
)

use dedicated process to determine if it is safe to watch folders
vladima added a commit that referenced this pull request Feb 16, 2017
…#14098) (#14124)

* use separate process to probe if drive is safe to watch (#14098)

use dedicated process to determine if it is safe to watch folders

* added release-2.2
@weswigham weswigham mentioned this pull request Jul 31, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TSServer Crashes when Opening file from Mapped Sharepoint Drive

4 participants