Revert to use refcount to keep track of directory watchers for failed lookup

This commit is contained in:
Sheetal Nandi 2017-09-05 14:19:40 -07:00
parent 6c6129361f
commit 7b2bab5b86
3 changed files with 104 additions and 83 deletions

View File

@ -43,6 +43,13 @@ namespace ts {
writeLog(s: string): void;
}
interface DirectoryWatchesOfFailedLookup {
/** watcher for the directory of failed lookup */
watcher: FileWatcher;
/** ref count keeping this directory watch alive */
refCount: number;
}
export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache {
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
let filesWithInvalidatedResolutions: Map<true> | undefined;
@ -56,8 +63,7 @@ namespace ts {
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
const directoryWatchesOfFailedLookups = createMap<FileWatcher>();
let hasChangesInFailedLookupLocations = false;
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
let rootDir: string;
let rootPath: Path;
@ -93,8 +99,7 @@ namespace ts {
}
function clear() {
clearMap(directoryWatchesOfFailedLookups, closeFileWatcher);
hasChangesInFailedLookupLocations = false;
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
closeTypeRootsWatch();
resolvedModuleNames.clear();
resolvedTypeReferenceDirectives.clear();
@ -122,17 +127,12 @@ namespace ts {
}
function finishCachingPerDirectoryResolution() {
if (hasChangesInFailedLookupLocations) {
const seenDirectories = createMap<true>();
watchFailedLookupLocationForCache(perDirectoryResolvedModuleNames, seenDirectories);
watchFailedLookupLocationForCache(perDirectoryResolvedTypeReferenceDirectives, seenDirectories);
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
if (!seenDirectories.has(path)) {
watcher.close();
}
});
hasChangesInFailedLookupLocations = false;
}
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
if (watcher.refCount === 0) {
directoryWatchesOfFailedLookups.delete(path);
watcher.watcher.close();
}
});
perDirectoryResolvedModuleNames.clear();
perDirectoryResolvedTypeReferenceDirectives.clear();
@ -171,7 +171,7 @@ namespace ts {
logChanges: boolean): R[] {
const path = resolutionHost.toPath(containingFile);
const currentResolutionsInFile = cache.get(path);
const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path);
const dirPath = getDirectoryPath(path);
let perDirectoryResolution = perDirectoryCache.get(dirPath);
if (!perDirectoryResolution) {
@ -179,53 +179,67 @@ namespace ts {
perDirectoryCache.set(dirPath, perDirectoryResolution);
}
const newResolutions: Map<T> = createMap<T>();
const resolvedModules: R[] = [];
const compilerOptions = resolutionHost.getCompilationSettings();
const seenNamesInFile = createMap<true>();
for (const name of names) {
// check if this is a duplicate entry in the list
let resolution = newResolutions.get(name);
if (!resolution) {
const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name);
if (existingResolution) {
// Remove from the cache since we would update the resolution in new file ourselves
currentResolutionsInFile.delete(name);
}
if (moduleResolutionIsValid(existingResolution)) {
// ok, it is safe to use existing name resolution results
resolution = existingResolution;
let resolution = resolutionsInFile.get(name);
if (!moduleResolutionIsValid(resolution, name)) {
const existingResolution = resolution;
const resolutionInDirectory = perDirectoryResolution.get(name);
if (resolutionInDirectory) {
resolution = resolutionInDirectory;
}
else {
const resolutionInDirectory = perDirectoryResolution && perDirectoryResolution.get(name);
if (resolutionInDirectory) {
resolution = resolutionInDirectory;
}
else {
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
perDirectoryResolution.set(name, resolution);
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
resolution.failedLookupLocations !== (existingResolution && existingResolution.failedLookupLocations);
}
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
perDirectoryResolution.set(name, resolution);
}
newResolutions.set(name, resolution);
resolutionsInFile.set(name, resolution);
const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations);
watchFailedLookupLocationOfResolution(resolution, diffIndex || 0);
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0);
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
filesWithChangedSetOfUnresolvedImports.push(path);
// reset log changes to avoid recording the same file multiple times
logChanges = false;
}
}
Debug.assert(resolution !== undefined);
Debug.assert(resolution !== undefined && !resolution.isInvalidated);
seenNamesInFile.set(name, true);
resolvedModules.push(getResult(resolution));
}
// replace old results with a new one
cache.set(path, newResolutions);
// Stop watching and remove the unused name
resolutionsInFile.forEach((resolution, name) => {
if (!seenNamesInFile.has(name)) {
stopWatchFailedLookupLocationOfResolution(resolution);
resolutionsInFile.delete(name);
}
});
return resolvedModules;
function moduleResolutionIsValid(resolution: T, name: string): boolean {
// This is already calculated resolution in this round of synchronization
if (seenNamesInFile.has(name)) {
return true;
}
if (!resolution || resolution.isInvalidated) {
return false;
}
const result = getResult(resolution);
if (result) {
return true;
}
// consider situation if we have no candidate locations as valid resolution.
// after all there is no point to invalidate it if we have no idea where to look for the module.
return resolution.failedLookupLocations.length === 0;
}
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
if (oldResolution === newResolution) {
return true;
@ -243,25 +257,9 @@ namespace ts {
}
return getResultFileName(oldResult) === getResultFileName(newResult);
}
function moduleResolutionIsValid(resolution: T): boolean {
if (!resolution || resolution.isInvalidated) {
return false;
}
const result = getResult(resolution);
if (result) {
return true;
}
// consider situation if we have no candidate locations as valid resolution.
// after all there is no point to invalidate it if we have no idea where to look for the module.
return resolution.failedLookupLocations.length === 0;
}
}
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
resolutionHost.writeLog(`resolveTypeReferenceDirectives[${resolvedTypeReferenceDirectives.size}"]: " + ${containingFile}`);
return resolveNamesWithLocalCache(
typeDirectiveNames, containingFile,
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
@ -271,7 +269,6 @@ namespace ts {
}
function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] {
resolutionHost.writeLog(`resolveModuleNames[${resolvedModuleNames.size}"]: " + ${containingFile}`);
return resolveNamesWithLocalCache(
moduleNames, containingFile,
resolvedModuleNames, perDirectoryResolvedModuleNames,
@ -322,22 +319,42 @@ namespace ts {
return { dir, dirPath };
}
function watchFailedLookupLocationForCache<T extends NameResolutionWithFailedLookupLocations>(
cache: Map<Map<T>>, seenDirectories: Map<true>
function watchFailedLookupLocationOfResolution<T extends NameResolutionWithFailedLookupLocations>(
resolution: T, startIndex?: number
) {
cache.forEach(value => value && value.forEach(
resolution => forEach(resolution && resolution.failedLookupLocations,
failedLookupLocation => {
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
if (!seenDirectories.has(dirPath)) {
if (!directoryWatchesOfFailedLookups.has(dirPath)) {
directoryWatchesOfFailedLookups.set(dirPath, createDirectoryWatcher(dir, dirPath));
}
seenDirectories.set(dirPath, true);
}
if (resolution && resolution.failedLookupLocations) {
for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) {
const failedLookupLocation = resolution.failedLookupLocations[i];
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
if (dirWatcher) {
dirWatcher.refCount++;
}
)
));
else {
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
}
}
}
}
function stopWatchFailedLookupLocationOfResolution<T extends NameResolutionWithFailedLookupLocations>(
resolution: T
) {
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
}
function stopWatchFailedLookupLocationOfResolutionFrom<T extends NameResolutionWithFailedLookupLocations>(
resolution: T, startIndex: number
) {
if (resolution && resolution.failedLookupLocations) {
for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) {
const failedLookupLocation = resolution.failedLookupLocations[i];
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
// Do not close the watcher yet since it might be needed by other failed lookup locations.
dirWatcher.refCount--;
}
}
}
function createDirectoryWatcher(directory: string, dirPath: Path) {
@ -371,8 +388,9 @@ namespace ts {
cache.forEach((value, path) => {
if (path === deletedFilePath) {
cache.delete(path);
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
value && forEachEntry(value, resolution => !!resolution.failedLookupLocations);
if (value) {
value.forEach(stopWatchFailedLookupLocationOfResolution);
}
}
else if (value) {
value.forEach(resolution => {

View File

@ -1214,16 +1214,17 @@ namespace ts.server {
}
private printProjects() {
if (!this.logger.hasLevel(LogLevel.verbose)) {
if (!this.logger.hasLevel(LogLevel.normal)) {
return;
}
const writeProjectFileNames = this.logger.hasLevel(LogLevel.verbose);
this.logger.startGroup();
let counter = 0;
const printProjects = (projects: Project[], counter: number): number => {
for (const project of projects) {
this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
this.logger.info(project.filesToString());
this.logger.info(project.filesToString(writeProjectFileNames));
this.logger.info("-----------------------------------------------");
counter++;
}

View File

@ -576,7 +576,7 @@ namespace ts.server {
if (!this.languageServiceEnabled) {
return undefined;
}
return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
}
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
@ -889,14 +889,16 @@ namespace ts.server {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
}
filesToString() {
filesToString(writeProjectFileNames: boolean) {
if (!this.program) {
return "\tFiles (0)\n";
}
const sourceFiles = this.program.getSourceFiles();
let strBuilder = `\tFiles (${sourceFiles.length})\n`;
for (const file of sourceFiles) {
strBuilder += `\t${file.fileName}\n`;
if (writeProjectFileNames) {
for (const file of sourceFiles) {
strBuilder += `\t${file.fileName}\n`;
}
}
return strBuilder;
}