Skip to content

Commit 7b2bab5

Browse files
committed
Revert to use refcount to keep track of directory watchers for failed lookup
1 parent 6c61293 commit 7b2bab5

File tree

3 files changed

+104
-83
lines changed

3 files changed

+104
-83
lines changed

src/compiler/resolutionCache.ts

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ namespace ts {
4343
writeLog(s: string): void;
4444
}
4545

46+
interface DirectoryWatchesOfFailedLookup {
47+
/** watcher for the directory of failed lookup */
48+
watcher: FileWatcher;
49+
/** ref count keeping this directory watch alive */
50+
refCount: number;
51+
}
52+
4653
export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache {
4754
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
4855
let filesWithInvalidatedResolutions: Map<true> | undefined;
@@ -56,8 +63,7 @@ namespace ts {
5663
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
5764
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
5865

59-
const directoryWatchesOfFailedLookups = createMap<FileWatcher>();
60-
let hasChangesInFailedLookupLocations = false;
66+
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
6167
let rootDir: string;
6268
let rootPath: Path;
6369

@@ -93,8 +99,7 @@ namespace ts {
9399
}
94100

95101
function clear() {
96-
clearMap(directoryWatchesOfFailedLookups, closeFileWatcher);
97-
hasChangesInFailedLookupLocations = false;
102+
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
98103
closeTypeRootsWatch();
99104
resolvedModuleNames.clear();
100105
resolvedTypeReferenceDirectives.clear();
@@ -122,17 +127,12 @@ namespace ts {
122127
}
123128

124129
function finishCachingPerDirectoryResolution() {
125-
if (hasChangesInFailedLookupLocations) {
126-
const seenDirectories = createMap<true>();
127-
watchFailedLookupLocationForCache(perDirectoryResolvedModuleNames, seenDirectories);
128-
watchFailedLookupLocationForCache(perDirectoryResolvedTypeReferenceDirectives, seenDirectories);
129-
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
130-
if (!seenDirectories.has(path)) {
131-
watcher.close();
132-
}
133-
});
134-
hasChangesInFailedLookupLocations = false;
135-
}
130+
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
131+
if (watcher.refCount === 0) {
132+
directoryWatchesOfFailedLookups.delete(path);
133+
watcher.watcher.close();
134+
}
135+
});
136136

137137
perDirectoryResolvedModuleNames.clear();
138138
perDirectoryResolvedTypeReferenceDirectives.clear();
@@ -171,61 +171,75 @@ namespace ts {
171171
logChanges: boolean): R[] {
172172

173173
const path = resolutionHost.toPath(containingFile);
174-
const currentResolutionsInFile = cache.get(path);
174+
const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path);
175175
const dirPath = getDirectoryPath(path);
176176
let perDirectoryResolution = perDirectoryCache.get(dirPath);
177177
if (!perDirectoryResolution) {
178178
perDirectoryResolution = createMap();
179179
perDirectoryCache.set(dirPath, perDirectoryResolution);
180180
}
181181

182-
const newResolutions: Map<T> = createMap<T>();
183182
const resolvedModules: R[] = [];
184183
const compilerOptions = resolutionHost.getCompilationSettings();
185184

185+
const seenNamesInFile = createMap<true>();
186186
for (const name of names) {
187187
// check if this is a duplicate entry in the list
188-
let resolution = newResolutions.get(name);
189-
if (!resolution) {
190-
const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name);
191-
if (existingResolution) {
192-
// Remove from the cache since we would update the resolution in new file ourselves
193-
currentResolutionsInFile.delete(name);
194-
}
195-
196-
if (moduleResolutionIsValid(existingResolution)) {
197-
// ok, it is safe to use existing name resolution results
198-
resolution = existingResolution;
188+
let resolution = resolutionsInFile.get(name);
189+
if (!moduleResolutionIsValid(resolution, name)) {
190+
const existingResolution = resolution;
191+
const resolutionInDirectory = perDirectoryResolution.get(name);
192+
if (resolutionInDirectory) {
193+
resolution = resolutionInDirectory;
199194
}
200195
else {
201-
const resolutionInDirectory = perDirectoryResolution && perDirectoryResolution.get(name);
202-
if (resolutionInDirectory) {
203-
resolution = resolutionInDirectory;
204-
}
205-
else {
206-
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
207-
perDirectoryResolution.set(name, resolution);
208-
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
209-
resolution.failedLookupLocations !== (existingResolution && existingResolution.failedLookupLocations);
210-
}
196+
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
197+
perDirectoryResolution.set(name, resolution);
211198
}
212-
newResolutions.set(name, resolution);
199+
resolutionsInFile.set(name, resolution);
200+
const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations);
201+
watchFailedLookupLocationOfResolution(resolution, diffIndex || 0);
202+
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0);
213203
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
214204
filesWithChangedSetOfUnresolvedImports.push(path);
215205
// reset log changes to avoid recording the same file multiple times
216206
logChanges = false;
217207
}
218208
}
219-
220-
Debug.assert(resolution !== undefined);
221-
209+
Debug.assert(resolution !== undefined && !resolution.isInvalidated);
210+
seenNamesInFile.set(name, true);
222211
resolvedModules.push(getResult(resolution));
223212
}
224213

225-
// replace old results with a new one
226-
cache.set(path, newResolutions);
214+
// Stop watching and remove the unused name
215+
resolutionsInFile.forEach((resolution, name) => {
216+
if (!seenNamesInFile.has(name)) {
217+
stopWatchFailedLookupLocationOfResolution(resolution);
218+
resolutionsInFile.delete(name);
219+
}
220+
});
221+
227222
return resolvedModules;
228223

224+
function moduleResolutionIsValid(resolution: T, name: string): boolean {
225+
// This is already calculated resolution in this round of synchronization
226+
if (seenNamesInFile.has(name)) {
227+
return true;
228+
}
229+
230+
if (!resolution || resolution.isInvalidated) {
231+
return false;
232+
}
233+
234+
const result = getResult(resolution);
235+
if (result) {
236+
return true;
237+
}
238+
// consider situation if we have no candidate locations as valid resolution.
239+
// after all there is no point to invalidate it if we have no idea where to look for the module.
240+
return resolution.failedLookupLocations.length === 0;
241+
}
242+
229243
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
230244
if (oldResolution === newResolution) {
231245
return true;
@@ -243,25 +257,9 @@ namespace ts {
243257
}
244258
return getResultFileName(oldResult) === getResultFileName(newResult);
245259
}
246-
247-
function moduleResolutionIsValid(resolution: T): boolean {
248-
if (!resolution || resolution.isInvalidated) {
249-
return false;
250-
}
251-
252-
const result = getResult(resolution);
253-
if (result) {
254-
return true;
255-
}
256-
257-
// consider situation if we have no candidate locations as valid resolution.
258-
// after all there is no point to invalidate it if we have no idea where to look for the module.
259-
return resolution.failedLookupLocations.length === 0;
260-
}
261260
}
262261

263262
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
264-
resolutionHost.writeLog(`resolveTypeReferenceDirectives[${resolvedTypeReferenceDirectives.size}"]: " + ${containingFile}`);
265263
return resolveNamesWithLocalCache(
266264
typeDirectiveNames, containingFile,
267265
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
@@ -271,7 +269,6 @@ namespace ts {
271269
}
272270

273271
function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] {
274-
resolutionHost.writeLog(`resolveModuleNames[${resolvedModuleNames.size}"]: " + ${containingFile}`);
275272
return resolveNamesWithLocalCache(
276273
moduleNames, containingFile,
277274
resolvedModuleNames, perDirectoryResolvedModuleNames,
@@ -322,22 +319,42 @@ namespace ts {
322319
return { dir, dirPath };
323320
}
324321

325-
function watchFailedLookupLocationForCache<T extends NameResolutionWithFailedLookupLocations>(
326-
cache: Map<Map<T>>, seenDirectories: Map<true>
322+
function watchFailedLookupLocationOfResolution<T extends NameResolutionWithFailedLookupLocations>(
323+
resolution: T, startIndex?: number
327324
) {
328-
cache.forEach(value => value && value.forEach(
329-
resolution => forEach(resolution && resolution.failedLookupLocations,
330-
failedLookupLocation => {
331-
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
332-
if (!seenDirectories.has(dirPath)) {
333-
if (!directoryWatchesOfFailedLookups.has(dirPath)) {
334-
directoryWatchesOfFailedLookups.set(dirPath, createDirectoryWatcher(dir, dirPath));
335-
}
336-
seenDirectories.set(dirPath, true);
337-
}
325+
if (resolution && resolution.failedLookupLocations) {
326+
for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) {
327+
const failedLookupLocation = resolution.failedLookupLocations[i];
328+
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
329+
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
330+
if (dirWatcher) {
331+
dirWatcher.refCount++;
332+
}
333+
else {
334+
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
338335
}
339-
)
340-
));
336+
}
337+
}
338+
}
339+
340+
function stopWatchFailedLookupLocationOfResolution<T extends NameResolutionWithFailedLookupLocations>(
341+
resolution: T
342+
) {
343+
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
344+
}
345+
346+
function stopWatchFailedLookupLocationOfResolutionFrom<T extends NameResolutionWithFailedLookupLocations>(
347+
resolution: T, startIndex: number
348+
) {
349+
if (resolution && resolution.failedLookupLocations) {
350+
for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) {
351+
const failedLookupLocation = resolution.failedLookupLocations[i];
352+
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation));
353+
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
354+
// Do not close the watcher yet since it might be needed by other failed lookup locations.
355+
dirWatcher.refCount--;
356+
}
357+
}
341358
}
342359

343360
function createDirectoryWatcher(directory: string, dirPath: Path) {
@@ -371,8 +388,9 @@ namespace ts {
371388
cache.forEach((value, path) => {
372389
if (path === deletedFilePath) {
373390
cache.delete(path);
374-
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
375-
value && forEachEntry(value, resolution => !!resolution.failedLookupLocations);
391+
if (value) {
392+
value.forEach(stopWatchFailedLookupLocationOfResolution);
393+
}
376394
}
377395
else if (value) {
378396
value.forEach(resolution => {

src/server/editorServices.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,16 +1214,17 @@ namespace ts.server {
12141214
}
12151215

12161216
private printProjects() {
1217-
if (!this.logger.hasLevel(LogLevel.verbose)) {
1217+
if (!this.logger.hasLevel(LogLevel.normal)) {
12181218
return;
12191219
}
12201220

1221+
const writeProjectFileNames = this.logger.hasLevel(LogLevel.verbose);
12211222
this.logger.startGroup();
12221223
let counter = 0;
12231224
const printProjects = (projects: Project[], counter: number): number => {
12241225
for (const project of projects) {
12251226
this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
1226-
this.logger.info(project.filesToString());
1227+
this.logger.info(project.filesToString(writeProjectFileNames));
12271228
this.logger.info("-----------------------------------------------");
12281229
counter++;
12291230
}

src/server/project.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ namespace ts.server {
576576
if (!this.languageServiceEnabled) {
577577
return undefined;
578578
}
579-
return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
579+
return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
580580
}
581581

582582
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
@@ -889,14 +889,16 @@ namespace ts.server {
889889
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
890890
}
891891

892-
filesToString() {
892+
filesToString(writeProjectFileNames: boolean) {
893893
if (!this.program) {
894894
return "\tFiles (0)\n";
895895
}
896896
const sourceFiles = this.program.getSourceFiles();
897897
let strBuilder = `\tFiles (${sourceFiles.length})\n`;
898-
for (const file of sourceFiles) {
899-
strBuilder += `\t${file.fileName}\n`;
898+
if (writeProjectFileNames) {
899+
for (const file of sourceFiles) {
900+
strBuilder += `\t${file.fileName}\n`;
901+
}
900902
}
901903
return strBuilder;
902904
}

0 commit comments

Comments
 (0)