mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-18 17:27:54 -05:00
use separate process to probe if drive is safe to watch (#14098)
use dedicated process to determine if it is safe to watch folders
This commit is contained in:
@@ -12,6 +12,7 @@ namespace ts.server {
|
||||
|
||||
const childProcess: {
|
||||
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
|
||||
execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike<string> }): string | Buffer;
|
||||
} = require("child_process");
|
||||
|
||||
const os: {
|
||||
@@ -59,7 +60,7 @@ namespace ts.server {
|
||||
|
||||
interface NodeChildProcess {
|
||||
send(message: any, sendHandle?: any): void;
|
||||
on(message: "message", f: (m: any) => void): void;
|
||||
on(message: "message" | "exit", f: (m: any) => void): void;
|
||||
kill(): void;
|
||||
pid: number;
|
||||
}
|
||||
@@ -576,7 +577,84 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
function extractWatchDirectoryCacheKey(path: string, currentDriveKey: string) {
|
||||
path = normalizeSlashes(path);
|
||||
if (isUNCPath(path)) {
|
||||
// UNC path: extract server name
|
||||
// //server/location
|
||||
// ^ <- from 0 to this position
|
||||
const firstSlash = path.indexOf(directorySeparator, 2);
|
||||
return firstSlash !== -1 ? path.substring(0, firstSlash).toLowerCase() : path;
|
||||
}
|
||||
const rootLength = getRootLength(path);
|
||||
if (rootLength === 0) {
|
||||
// relative path - assume file is on the current drive
|
||||
return currentDriveKey;
|
||||
}
|
||||
if (path.charCodeAt(1) === CharacterCodes.colon && path.charCodeAt(2) === CharacterCodes.slash) {
|
||||
// rooted path that starts with c:/... - extract drive letter
|
||||
return path.charAt(0).toLowerCase();
|
||||
}
|
||||
if (path.charCodeAt(0) === CharacterCodes.slash && path.charCodeAt(1) !== CharacterCodes.slash) {
|
||||
// rooted path that starts with slash - /somename - use key for current drive
|
||||
return currentDriveKey;
|
||||
}
|
||||
// do not cache any other cases
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isUNCPath(s: string): boolean {
|
||||
return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash;
|
||||
}
|
||||
|
||||
const sys = <ServerHost>ts.sys;
|
||||
// use watchGuard process on Windows when node version is 4 or later
|
||||
const useWatchGuard = process.platform === "win32" && getNodeMajorVersion() >= 4;
|
||||
if (useWatchGuard) {
|
||||
const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined);
|
||||
const statusCache = createMap<boolean>();
|
||||
const originalWatchDirectory = sys.watchDirectory;
|
||||
sys.watchDirectory = function (path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
|
||||
const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive);
|
||||
let status = cacheKey && statusCache.get(cacheKey);
|
||||
if (status === undefined) {
|
||||
if (logger.hasLevel(LogLevel.verbose)) {
|
||||
logger.info(`${cacheKey} for path ${path} not found in cache...`);
|
||||
}
|
||||
try {
|
||||
const args = [combinePaths(__dirname, "watchGuard.js"), path];
|
||||
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" } });
|
||||
status = true;
|
||||
if (logger.hasLevel(LogLevel.verbose)) {
|
||||
logger.info(`WatchGuard for path ${path} returned: OK`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
status = false;
|
||||
if (logger.hasLevel(LogLevel.verbose)) {
|
||||
logger.info(`WatchGuard for path ${path} returned: ${e.message}`);
|
||||
}
|
||||
}
|
||||
if (cacheKey) {
|
||||
statusCache.set(cacheKey, status);
|
||||
}
|
||||
}
|
||||
else if (logger.hasLevel(LogLevel.verbose)) {
|
||||
logger.info(`watchDirectory for ${path} uses cached drive information.`);
|
||||
}
|
||||
if (status) {
|
||||
// this drive is safe to use - call real 'watchDirectory'
|
||||
return originalWatchDirectory.call(sys, path, callback, recursive);
|
||||
}
|
||||
else {
|
||||
// this drive is unsafe - return no-op watcher
|
||||
return { close() { } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override sys.write because fs.writeSync is not reliable on Node 4
|
||||
sys.write = (s: string) => writeMessage(new Buffer(s, "utf8"));
|
||||
|
||||
10
src/server/watchGuard/tsconfig.json
Normal file
10
src/server/watchGuard/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"removeComments": true,
|
||||
"outFile": "../../../built/local/watchGuard.js"
|
||||
},
|
||||
"files": [
|
||||
"watchGuard.ts"
|
||||
]
|
||||
}
|
||||
19
src/server/watchGuard/watchGuard.ts
Normal file
19
src/server/watchGuard/watchGuard.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
process.exit(1);
|
||||
}
|
||||
const directoryName = process.argv[2];
|
||||
const fs: { watch(directoryName: string, options: any, callback: () => {}): any } = require("fs");
|
||||
// main reason why we need separate process to check if it is safe to watch some path
|
||||
// is to guard against crashes that cannot be intercepted with protected blocks and
|
||||
// code in tsserver already can handle normal cases, like non-existing folders.
|
||||
// This means that here we treat any result (success or exception) from fs.watch as success since it does not tear down the process.
|
||||
// The only case that should be considered as failure - when watchGuard process crashes.
|
||||
try {
|
||||
const watcher = fs.watch(directoryName, { recursive: true }, () => ({}))
|
||||
watcher.close();
|
||||
}
|
||||
catch (_e) {
|
||||
}
|
||||
process.exit(0);
|
||||
Reference in New Issue
Block a user