-
Notifications
You must be signed in to change notification settings - Fork 13k
Fix too many watcher instances issue #6026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
e675744
5fa7bec
36cc0e0
d419015
602cde4
178b2da
caa6eb4
631363f
56a7217
8cf1a34
697644c
114d2bd
d22626f
02531af
067573b
dbfe862
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,7 @@ namespace ts { | |
interface WatchedFile { | ||
fileName: string; | ||
callback: (fileName: string, removed?: boolean) => void; | ||
mtime: Date; | ||
mtime?: Date; | ||
} | ||
|
||
export interface FileWatcher { | ||
|
@@ -218,7 +218,7 @@ namespace ts { | |
|
||
// average async stat takes about 30 microseconds | ||
// set chunk size to do 30 files in < 1 millisecond | ||
function createWatchedFileSet(interval = 2500, chunkSize = 30) { | ||
function createPollingWatchedFileSet(interval = 2500, chunkSize = 30) { | ||
let watchedFiles: WatchedFile[] = []; | ||
let nextFileToCheck = 0; | ||
let watchTimer: any; | ||
|
@@ -293,6 +293,50 @@ namespace ts { | |
removeFile: removeFile | ||
}; | ||
} | ||
|
||
function createWatchedFileSet() { | ||
let watchedDirectories: { [path: string]: FileWatcher } = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use FileMap instead. |
||
let watchedFiles: { [fileName: string]: (fileName: string, removed?: boolean) => void; } = {}; | ||
|
||
function addFile(fileName: string, callback: (fileName: string, removed?: boolean) => void): WatchedFile { | ||
const file: WatchedFile = { fileName, callback }; | ||
let watchedPaths = Object.keys(watchedDirectories); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure that I understand why do we use map-list structure if ultimately it is still doing linear scan over the array of keys (getting those also incur extra array allocation) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because each directory path corresponds to a filewatcher that we may need to call its |
||
// Try to find parent paths that are already watched. If found, don't add directory watchers | ||
let watchedParentPaths = watchedPaths.filter(path => fileName.indexOf(path) === 0); | ||
// If adding new watchers, try to find children paths that are already watched. If found, close them. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "If adding new watchers, we need to close any existing watchers for subdirectories." |
||
if (watchedParentPaths.length === 0) { | ||
let pathToWatch = ts.getDirectoryPath(fileName); | ||
for (let watchedPath in watchedDirectories) { | ||
if (watchedPath.indexOf(pathToWatch) === 0) { | ||
watchedDirectories[watchedPath].close(); | ||
delete watchedDirectories[watchedPath]; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider replacing these four lines with: removeDirWatcher(getDirectoryPath(filePath)); |
||
} | ||
watchedDirectories[pathToWatch] = _fs.watch( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it sloppy in regards of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true and this is one more argument to switch to |
||
pathToWatch, | ||
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, ts.normalizePath(ts.combinePaths(pathToWatch, relativeFileName))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to do this every time? is the relativeFileName expected to change between invocations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thing is we don't know what the relative file name is "relative" to. For example, we may have a watcher for |
||
); | ||
} | ||
watchedFiles[fileName] = callback; | ||
return { fileName, callback } | ||
} | ||
|
||
function removeFile(file: WatchedFile) { | ||
delete watchedFiles[file.fileName]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but we are still watching the directory anyways? when does a directory watcher ever die? |
||
} | ||
|
||
function fileEventHandler(eventName: string, fileName: string) { | ||
if (watchedFiles[fileName]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hasProperty |
||
let callback = watchedFiles[fileName]; | ||
callback(fileName); | ||
} | ||
} | ||
|
||
return { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this prior to the function declarations |
||
addFile: addFile, | ||
removeFile: removeFile | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use shorthand properties to avoid repetition, or move the function declaration to use method syntax. |
||
} | ||
} | ||
|
||
// REVIEW: for now this implementation uses polling. | ||
// The advantage of polling is that it works reliably | ||
|
@@ -307,6 +351,7 @@ namespace ts { | |
// 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 watchedFileSet = createWatchedFileSet(); | ||
|
||
function isNode4OrLater(): Boolean { | ||
|
@@ -411,14 +456,10 @@ namespace ts { | |
// and is more efficient than `fs.watchFile` (ref: https://github.com/nodejs/node/pull/2649 | ||
// and https://github.com/Microsoft/TypeScript/issues/4643), therefore | ||
// if the current node.js version is newer than 4, use `fs.watch` instead. | ||
if (isNode4OrLater()) { | ||
// Note: in node the callback of fs.watch is given only the relative file name as a parameter | ||
return _fs.watch(fileName, (eventName: string, relativeFileName: string) => callback(fileName)); | ||
} | ||
|
||
const watchedFile = watchedFileSet.addFile(fileName, callback); | ||
let fileSet = isNode4OrLater() ? watchedFileSet : pollingWatchedFileSet; | ||
const watchedFile = fileSet.addFile(fileName, callback); | ||
return { | ||
close: () => watchedFileSet.removeFile(watchedFile) | ||
close: () => fileSet.removeFile(watchedFile) | ||
}; | ||
}, | ||
watchDirectory: (path, callback, recursive) => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so why do we need this Polling one, why can not we just fall back to fs.watchFile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our polling mechanism is different from
fs.watchFile
.fs.watchFile
polls each file independently, so if you got many files to watch, it might increase the work load a lot. In our approach, we poll a fixed number of files (chunkSize
) each number, so if thechunkSize
is 30 and you have 90 files to watch, the work load is still the same for each polling, only that changes will be detected after 3 interval time instead of just 1. This way we keep the polling work load consistent