Merge pull request #8196 from zhengbli/fileWatcherBackToPolling

Change file watching back to polling.
This commit is contained in:
Zhengbo Li
2016-04-25 11:42:26 -07:00
3 changed files with 130 additions and 113 deletions

View File

@@ -153,6 +153,98 @@ namespace ts.server {
// This places log file in the directory containing editorServices.js
// TODO: check that this location is writable
// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
function createPollingWatchedFileSet(interval = 2500, chunkSize = 30) {
let watchedFiles: WatchedFile[] = [];
let nextFileToCheck = 0;
let watchTimer: any;
function getModifiedTime(fileName: string): Date {
return fs.statSync(fileName).mtime;
}
function poll(checkedIndex: number) {
const watchedFile = watchedFiles[checkedIndex];
if (!watchedFile) {
return;
}
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
if (err) {
watchedFile.callback(watchedFile.fileName);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
}
});
}
// this implementation uses polling and
// stat due to inconsistencies of fs.watch
// and efficiency of stat on modern filesystems
function startWatchTimer() {
watchTimer = setInterval(() => {
let count = 0;
let nextToCheck = nextFileToCheck;
let firstCheck = -1;
while ((count < chunkSize) && (nextToCheck !== firstCheck)) {
poll(nextToCheck);
if (firstCheck < 0) {
firstCheck = nextToCheck;
}
nextToCheck++;
if (nextToCheck === watchedFiles.length) {
nextToCheck = 0;
}
count++;
}
nextFileToCheck = nextToCheck;
}, interval);
}
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
const file: WatchedFile = {
fileName,
callback,
mtime: getModifiedTime(fileName)
};
watchedFiles.push(file);
if (watchedFiles.length === 1) {
startWatchTimer();
}
return file;
}
function removeFile(file: WatchedFile) {
watchedFiles = copyListRemovingItem(file, watchedFiles);
}
return {
getModifiedTime: getModifiedTime,
poll: poll,
startWatchTimer: startWatchTimer,
addFile: addFile,
removeFile: removeFile
};
}
// REVIEW: for now this implementation uses polling.
// The advantage of polling is that it works reliably
// on all os and with network mounted files.
// For 90 referenced files, the average time to detect
// changes is 2*msInterval (by default 5 seconds).
// The overhead of this is .04 percent (1/2500) with
// average pause of < 1 millisecond (and max
// pause less than 1.5 milliseconds); question is
// do we anticipate reference sets in the 100s and
// do we care about waiting 10-20 seconds to detect
// changes for large reference sets? If so, do we want
// to increase the chunk size or decrease the interval
// time dynamically to match the large reference set?
const pollingWatchedFileSet = createPollingWatchedFileSet();
const logger = createLoggerFromEnv();
const pending: string[] = [];
@@ -176,6 +268,12 @@ namespace ts.server {
// Override sys.write because fs.writeSync is not reliable on Node 4
ts.sys.write = (s: string) => writeMessage(s);
ts.sys.watchFile = (fileName, callback) => {
const watchedFile = pollingWatchedFileSet.addFile(fileName, callback);
return {
close: () => pollingWatchedFileSet.removeFile(watchedFile)
};
};
const ioSession = new IOSession(ts.sys, logger);
process.on("uncaughtException", function(err: Error) {