From 5aafd3f06c2cd8919d0fa4b168bd6f695666a1df Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 23 Aug 2017 13:34:41 -0700 Subject: [PATCH] Reduce number of watches for failed lookup locations as part of module resolution --- src/compiler/resolutionCache.ts | 78 ++++++++++++++++++++------------- src/compiler/utilities.ts | 12 +++++ src/compiler/watchedProgram.ts | 22 +++++++--- src/server/editorServices.ts | 4 +- src/server/project.ts | 26 ++++++----- 5 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 8001dd81bd9..dd03a055804 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void; + onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean; createHasInvalidatedResolution(): HasInvalidatedResolution; @@ -26,15 +26,17 @@ namespace ts { isInvalidated?: boolean; } - interface FailedLookupLocationsWatcher { + interface DirectoryWatchesOfFailedLookup { + /** watcher for the directory of failed lookup */ watcher: FileWatcher; - refCount: number; + /** map with key being the failed lookup location path and value being the actual location */ + mapLocations: MultiMap; } export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher, + watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher, log: (s: string) => void, projectName?: string, getGlobalCache?: () => string | undefined): ResolutionCache { @@ -49,7 +51,7 @@ namespace ts { const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); - const failedLookupLocationsWatches = createMap(); + const directoryWatchesOfFailedLookups = createMap(); return { setModuleResolutionHost, @@ -58,7 +60,7 @@ namespace ts { resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, - invalidateResolutionOfChangedFailedLookupLocation, + onFileAddOrRemoveInDirectoryOfFailedLookup, createHasInvalidatedResolution, clear }; @@ -69,7 +71,7 @@ namespace ts { function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache - clearMap(failedLookupLocationsWatches, closeFileWatcherOf); + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -203,43 +205,51 @@ namespace ts { } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); - if (failedLookupLocationWatcher) { - failedLookupLocationWatcher.refCount++; - log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`); + const dirPath = getDirectoryPath(failedLookupLocationPath); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + if (watches) { + watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation); } else { - const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 }); + const mapLocations = createMultiMap(); + mapLocations.add(failedLookupLocationPath, failedLookupLocation); + directoryWatchesOfFailedLookups.set(dirPath, { + watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)), + mapLocations + }); } } function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); - Debug.assert(!!failedLookupLocationWatcher); - failedLookupLocationWatcher.refCount--; - if (failedLookupLocationWatcher.refCount) { - log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`); - } - else { - failedLookupLocationWatcher.watcher.close(); - failedLookupLocationsWatches.delete(failedLookupLocationPath); + const dirPath = getDirectoryPath(failedLookupLocationPath); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation); + if (watches.mapLocations.size === 0) { + watches.watcher.close(); + directoryWatchesOfFailedLookups.delete(dirPath); } } type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void; - function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction) { - forEach(failedLookupLocations, failedLookupLocation => { - fn(failedLookupLocation, toPath(failedLookupLocation)); - }); + function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction, startIndex?: number) { + if (failedLookupLocations) { + for (let i = startIndex || 0; i < failedLookupLocations.length; i++) { + fn(failedLookupLocations[i], toPath(failedLookupLocations[i])); + } + } } function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { + log(`Resolution cache: Updating...`); + const index = existingFailedLookupLocations && failedLookupLocations ? + findDiffIndex(failedLookupLocations, existingFailedLookupLocations) : + 0; + // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation); + withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation, index); // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher); + withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher, index); } function invalidateResolutionCacheOfDeletedFile( @@ -291,9 +301,15 @@ namespace ts { invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } - function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) { - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives); + function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path) { + const dirPath = getDirectoryPath(fileOrFolder); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); + if (isFailedLookupFile) { + invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames); + invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives); + } + return isFailedLookupFile; } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7e43d793f0b..641caa0ce57 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3543,6 +3543,18 @@ namespace ts { } } + /** + * Find the index where arrayA and arrayB differ + */ + export function findDiffIndex(arrayA: ReadonlyArray, arrayB: ReadonlyArray) { + for (let i = 0; i < arrayA.length; i++) { + if (i === arrayB.length || arrayA[i] !== arrayB[i]) { + return i; + } + } + return arrayA.length; + } + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { return host.watchFile(file, cb); } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 2a23e66763b..77c67325ef9 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -268,7 +268,7 @@ namespace ts { const resolutionCache = createResolutionCache( fileName => toPath(fileName), () => compilerOptions, - watchFailedLookupLocation, + watchDirectoryOfFailedLookupLocation, writeLog ); @@ -552,14 +552,22 @@ namespace ts { } } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog); + function watchDirectoryOfFailedLookupLocation(directory: string) { + return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog); } - function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { - updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); - resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - scheduleProgramUpdate(); + function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { + const fileOrFolderPath = toPath(fileOrFolder); + + if (configFileName) { + // Since the file existance changed, update the sourceFiles cache + (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + scheduleProgramUpdate(); + } } function watchMissingFilePath(missingFilePath: Path) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 292f6e3b90a..73912b3bd91 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -432,8 +432,8 @@ namespace ts.server { return s => this.logger.info(s + detailedInfo); } - toPath(fileName: string, basePath = this.currentDirectory) { - return toPath(fileName, basePath, this.toCanonicalFileName); + toPath(fileName: string) { + return toPath(fileName, this.currentDirectory, this.toCanonicalFileName); } /* @internal */ diff --git a/src/server/project.ts b/src/server/project.ts index ec58e4cb88d..420d080b9f2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -216,7 +216,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - (failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath), + directory => this.watchDirectoryOfFailedLookup(directory), s => this.projectService.logger.info(s), this.getProjectName(), () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined @@ -233,24 +233,30 @@ namespace ts.server { this.markAsDirty(); } - private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - return this.projectService.watchFile( + private watchDirectoryOfFailedLookup(directory: string) { + return this.projectService.watchDirectory( this.projectService.host, - failedLookupLocation, - (fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath), + directory, + fileOrFolder => this.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder), + WatchDirectoryFlags.None, WatchType.FailedLookupLocation, this ); } - private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { + private onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { + const fileOrFolderPath = this.projectService.toPath(fileOrFolder); + // There is some kind of change in the failed lookup location, update the program if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); + (this.lsHost.host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (this.resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } private setInternalCompilerOptionsForEmittingJsFiles() {