Skip to content

Commit 8d5d4c2

Browse files
committed
Reduce storage of maps/sets for failed lookups
1 parent 2b97b2c commit 8d5d4c2

File tree

2 files changed

+65
-135
lines changed

2 files changed

+65
-135
lines changed

src/compiler/resolutionCache.ts

Lines changed: 63 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ namespace ts {
3030
isInvalidated?: boolean;
3131
}
3232

33-
interface DirectoryWatchesOfFailedLookup {
34-
/** watcher for the directory of failed lookup */
35-
watcher: FileWatcher;
36-
/** map with key being the failed lookup location path and value being the actual location */
37-
mapLocations: Map<number>;
38-
}
39-
4033
export interface ResolutionCacheHost extends ModuleResolutionHost {
4134
toPath(fileName: string): Path;
4235
getCompilationSettings(): CompilerOptions;
@@ -61,11 +54,10 @@ namespace ts {
6154
const perDirectoryResolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
6255
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
6356
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
64-
6557
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
6658

67-
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
68-
const failedLookupLocationToDirPath = createMap<Path>();
59+
const directoryWatchesOfFailedLookups = createMap<FileWatcher>();
60+
let hasChangesInFailedLookupLocations = false;
6961
let rootDir: string;
7062
let rootPath: Path;
7163

@@ -101,9 +93,8 @@ namespace ts {
10193
}
10294

10395
function clear() {
104-
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
105-
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
106-
failedLookupLocationToDirPath.clear();
96+
clearMap(directoryWatchesOfFailedLookups, closeFileWatcher);
97+
hasChangesInFailedLookupLocations = false;
10798
closeTypeRootsWatch();
10899
resolvedModuleNames.clear();
109100
resolvedTypeReferenceDirectives.clear();
@@ -131,6 +122,18 @@ namespace ts {
131122
}
132123

133124
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+
}
136+
134137
perDirectoryResolvedModuleNames.clear();
135138
perDirectoryResolvedTypeReferenceDirectives.clear();
136139
}
@@ -202,8 +205,9 @@ namespace ts {
202205
else {
203206
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
204207
perDirectoryResolution.set(name, resolution);
208+
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
209+
resolution.failedLookupLocations !== (existingResolution && existingResolution.failedLookupLocations);
205210
}
206-
updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations);
207211
}
208212
newResolutions.set(name, resolution);
209213
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
@@ -218,11 +222,6 @@ namespace ts {
218222
resolvedModules.push(getResult(resolution));
219223
}
220224

221-
// Close all the file watchers for the names that arent required any more
222-
if (currentResolutionsInFile) {
223-
clearMap(currentResolutionsInFile, resolution => withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher));
224-
}
225-
226225
// replace old results with a new one
227226
cache.set(path, newResolutions);
228227
return resolvedModules;
@@ -262,6 +261,7 @@ namespace ts {
262261
}
263262

264263
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
264+
resolutionHost.writeLog(`resolveTypeReferenceDirectives[${resolvedTypeReferenceDirectives.size}"]: " + ${containingFile}`);
265265
return resolveNamesWithLocalCache(
266266
typeDirectiveNames, containingFile,
267267
resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives,
@@ -271,6 +271,7 @@ namespace ts {
271271
}
272272

273273
function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] {
274+
resolutionHost.writeLog(`resolveModuleNames[${resolvedModuleNames.size}"]: " + ${containingFile}`);
274275
return resolveNamesWithLocalCache(
275276
moduleNames, containingFile,
276277
resolvedModuleNames, perDirectoryResolvedModuleNames,
@@ -279,35 +280,19 @@ namespace ts {
279280
);
280281
}
281282

282-
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
283-
const failedLocationDirPath = getDirectoryPath(failedLookupLocationPath);
284-
const cachedDirPath = failedLookupLocationToDirPath.get(failedLocationDirPath);
285-
if (cachedDirPath) {
286-
watchFailedLookupLocationInDirectory(cachedDirPath, failedLookupLocationPath, /*dir*/ undefined);
287-
return;
288-
}
283+
function isNodeModulesDirectory(dirPath: Path) {
284+
return endsWith(dirPath, "/node_modules");
285+
}
289286

287+
type DirectoryOfFailedLookupWatch = { dir: string; dirPath: Path; };
288+
function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch {
290289
if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
291-
// Watch in directory of rootPath
292-
failedLookupLocationToDirPath.set(failedLocationDirPath, rootPath);
293-
watchFailedLookupLocationInDirectory(rootPath, failedLookupLocationPath, rootDir);
294-
return;
290+
return { dir: rootDir, dirPath: rootPath };
295291
}
296292

297-
const { dir, dirPath } = getDirectoryToWatchWhenNotInRootPath(
298-
getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())),
299-
getDirectoryPath(failedLookupLocationPath)
300-
);
301-
failedLookupLocationToDirPath.set(failedLocationDirPath, dirPath);
302-
watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir);
303-
}
304-
305-
function isNodeModulesDirectory(dirPath: Path) {
306-
const nodemodules = "/node_modules";
307-
return endsWith(dirPath, nodemodules);
308-
}
293+
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()));
294+
let dirPath = getDirectoryPath(failedLookupLocationPath);
309295

310-
function getDirectoryToWatchWhenNotInRootPath(dir: string, dirPath: Path): { dir: string, dirPath: Path } {
311296
// If the directory is node_modules use it to watch
312297
if (isNodeModulesDirectory(dirPath)) {
313298
return { dir, dirPath };
@@ -337,21 +322,22 @@ namespace ts {
337322
return { dir, dirPath };
338323
}
339324

340-
function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocationPath: Path, dir: string | undefined) {
341-
const watches = directoryWatchesOfFailedLookups.get(dirPath);
342-
if (watches) {
343-
const existingCount = watches.mapLocations.get(failedLookupLocationPath) || 0;
344-
watches.mapLocations.set(failedLookupLocationPath, existingCount + 1);
345-
}
346-
else {
347-
Debug.assert(dir !== undefined);
348-
const mapLocations = createMap<number>();
349-
mapLocations.set(failedLookupLocationPath, 1);
350-
directoryWatchesOfFailedLookups.set(dirPath, {
351-
watcher: createDirectoryWatcher(dir, dirPath),
352-
mapLocations
353-
});
354-
}
325+
function watchFailedLookupLocationForCache<T extends NameResolutionWithFailedLookupLocations>(
326+
cache: Map<Map<T>>, seenDirectories: Map<true>
327+
) {
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+
}
338+
}
339+
)
340+
));
355341
}
356342

357343
function createDirectoryWatcher(directory: string, dirPath: Path) {
@@ -365,62 +351,18 @@ namespace ts {
365351
// If the files are added to project root or node_modules directory, always run through the invalidation process
366352
// Otherwise run through invalidation only if adding to the immediate directory
367353
if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) {
368-
// If the location results in update to failed lookup, schedule program update
369-
if (dirPath === fileOrFolderPath) {
370-
if (onAddOrRemoveDirectoryOfFailedLookup(dirPath)) {
371-
resolutionHost.onInvalidatedResolution();
372-
}
373-
}
374-
else if (onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath, fileOrFolderPath)) {
354+
const isChangedFailedLookupLocation: (location: string) => boolean = dirPath === fileOrFolderPath ?
355+
// If the file watched directory is created/deleted invalidate any resolution has failed lookup in this directory
356+
location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)) :
357+
// Otherwise only the resolutions referencing the file or folder added
358+
location => resolutionHost.toPath(location) === fileOrFolderPath;
359+
if (invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation)) {
375360
resolutionHost.onInvalidatedResolution();
376361
}
377362
}
378363
}, WatchDirectoryFlags.Recursive);
379364
}
380365

381-
function closeFailedLookupLocationWatcher(_failedLookupLocation: string, failedLookupLocationPath: Path) {
382-
const failedLookupDirectory = getDirectoryPath(failedLookupLocationPath);
383-
const dirPath = failedLookupLocationToDirPath.get(failedLookupDirectory);
384-
const watches = directoryWatchesOfFailedLookups.get(dirPath);
385-
const refCount = watches.mapLocations.get(failedLookupLocationPath);
386-
if (refCount === 1) {
387-
// If this was last failed lookup location being tracked by the dir watcher,
388-
// remove the failed lookup location path to dir Path entry
389-
watches.mapLocations.delete(failedLookupLocationPath);
390-
391-
// If there are no more files that need this watcher alive, close the watcher
392-
if (watches.mapLocations.size === 0) {
393-
watches.watcher.close();
394-
directoryWatchesOfFailedLookups.delete(dirPath);
395-
failedLookupLocationToDirPath.delete(failedLookupDirectory);
396-
}
397-
}
398-
else {
399-
watches.mapLocations.set(failedLookupLocationPath, refCount - 1);
400-
}
401-
}
402-
403-
type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void;
404-
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string> | undefined, fn: FailedLookupLocationAction, startIndex?: number) {
405-
if (failedLookupLocations) {
406-
for (let i = startIndex || 0; i < failedLookupLocations.length; i++) {
407-
fn(failedLookupLocations[i], resolutionHost.toPath(failedLookupLocations[i]));
408-
}
409-
}
410-
}
411-
412-
function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray<string> | undefined, existingFailedLookupLocations: ReadonlyArray<string> | undefined) {
413-
const index = existingFailedLookupLocations && failedLookupLocations ?
414-
findDiffIndex(failedLookupLocations, existingFailedLookupLocations) :
415-
0;
416-
417-
// Watch all the failed lookup locations
418-
withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation, index);
419-
420-
// Close existing watches for the failed locations
421-
withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher, index);
422-
}
423-
424366
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
425367
deletedFilePath: Path,
426368
cache: Map<Map<T>>,
@@ -429,9 +371,8 @@ namespace ts {
429371
cache.forEach((value, path) => {
430372
if (path === deletedFilePath) {
431373
cache.delete(path);
432-
value.forEach(resolution => {
433-
withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher);
434-
});
374+
hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations ||
375+
value && forEachEntry(value, resolution => !!resolution.failedLookupLocations);
435376
}
436377
else if (value) {
437378
value.forEach(resolution => {
@@ -449,7 +390,12 @@ namespace ts {
449390
});
450391
}
451392

452-
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
393+
function invalidateResolutionOfFile(filePath: Path) {
394+
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName);
395+
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
396+
}
397+
398+
function invalidateResolutionCacheOfFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
453399
cache: Map<Map<T>>,
454400
isChangedFailedLookupLocation: (location: string) => boolean
455401
) {
@@ -466,29 +412,11 @@ namespace ts {
466412
});
467413
}
468414

469-
function invalidateResolutionOfFile(filePath: Path) {
470-
invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName);
471-
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
472-
}
473-
474-
function onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath: Path, fileOrFolder: Path) {
475-
const watches = directoryWatchesOfFailedLookups.get(dirPath);
476-
if (watches.mapLocations.has(fileOrFolder)) {
477-
const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
478-
const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder;
479-
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder);
480-
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder);
481-
return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles;
482-
}
483-
return false;
484-
}
485-
486-
function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) {
487-
const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
488-
const isInDirPath: (location: string) => boolean = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location));
489-
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath);
490-
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath);
491-
filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles;
415+
function invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation: (location: string) => boolean) {
416+
const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
417+
invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, isChangedFailedLookupLocation);
418+
invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, isChangedFailedLookupLocation);
419+
return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount;
492420
}
493421

494422
function closeTypeRootsWatch() {

src/server/project.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ namespace ts.server {
789789
private updateGraphWorker() {
790790
const oldProgram = this.program;
791791

792+
this.writeLog(`Starting Update graph worker: Project: ${this.getProjectName()}`);
792793
this.resolutionCache.startCachingPerDirectoryResolution();
793794
this.program = this.languageService.getProgram();
794795
this.resolutionCache.finishCachingPerDirectoryResolution();
@@ -843,6 +844,7 @@ namespace ts.server {
843844
}
844845
});
845846

847+
this.writeLog(`Finishing Update graph worker: Project: ${this.getProjectName()}`);
846848
return hasChanges;
847849
}
848850

0 commit comments

Comments
 (0)