Skip to content

Commit 5aafd3f

Browse files
committed
Reduce number of watches for failed lookup locations as part of module resolution
1 parent 4c79033 commit 5aafd3f

5 files changed

Lines changed: 92 additions & 50 deletions

File tree

src/compiler/resolutionCache.ts

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace ts {
1414
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
1515

1616
invalidateResolutionOfFile(filePath: Path): void;
17-
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void;
17+
onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean;
1818

1919
createHasInvalidatedResolution(): HasInvalidatedResolution;
2020

@@ -26,15 +26,17 @@ namespace ts {
2626
isInvalidated?: boolean;
2727
}
2828

29-
interface FailedLookupLocationsWatcher {
29+
interface DirectoryWatchesOfFailedLookup {
30+
/** watcher for the directory of failed lookup */
3031
watcher: FileWatcher;
31-
refCount: number;
32+
/** map with key being the failed lookup location path and value being the actual location */
33+
mapLocations: MultiMap<string>;
3234
}
3335

3436
export function createResolutionCache(
3537
toPath: (fileName: string) => Path,
3638
getCompilerOptions: () => CompilerOptions,
37-
watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher,
39+
watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher,
3840
log: (s: string) => void,
3941
projectName?: string,
4042
getGlobalCache?: () => string | undefined): ResolutionCache {
@@ -49,7 +51,7 @@ namespace ts {
4951
const resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
5052
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
5153

52-
const failedLookupLocationsWatches = createMap<FailedLookupLocationsWatcher>();
54+
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
5355

5456
return {
5557
setModuleResolutionHost,
@@ -58,7 +60,7 @@ namespace ts {
5860
resolveModuleNames,
5961
resolveTypeReferenceDirectives,
6062
invalidateResolutionOfFile,
61-
invalidateResolutionOfChangedFailedLookupLocation,
63+
onFileAddOrRemoveInDirectoryOfFailedLookup,
6264
createHasInvalidatedResolution,
6365
clear
6466
};
@@ -69,7 +71,7 @@ namespace ts {
6971

7072
function clear() {
7173
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
72-
clearMap(failedLookupLocationsWatches, closeFileWatcherOf);
74+
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
7375
resolvedModuleNames.clear();
7476
resolvedTypeReferenceDirectives.clear();
7577
}
@@ -203,43 +205,51 @@ namespace ts {
203205
}
204206

205207
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
206-
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
207-
if (failedLookupLocationWatcher) {
208-
failedLookupLocationWatcher.refCount++;
209-
log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`);
208+
const dirPath = getDirectoryPath(failedLookupLocationPath);
209+
const watches = directoryWatchesOfFailedLookups.get(dirPath);
210+
if (watches) {
211+
watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation);
210212
}
211213
else {
212-
const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
213-
failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 });
214+
const mapLocations = createMultiMap<string>();
215+
mapLocations.add(failedLookupLocationPath, failedLookupLocation);
216+
directoryWatchesOfFailedLookups.set(dirPath, {
217+
watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)),
218+
mapLocations
219+
});
214220
}
215221
}
216222

217223
function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) {
218-
const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath);
219-
Debug.assert(!!failedLookupLocationWatcher);
220-
failedLookupLocationWatcher.refCount--;
221-
if (failedLookupLocationWatcher.refCount) {
222-
log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`);
223-
}
224-
else {
225-
failedLookupLocationWatcher.watcher.close();
226-
failedLookupLocationsWatches.delete(failedLookupLocationPath);
224+
const dirPath = getDirectoryPath(failedLookupLocationPath);
225+
const watches = directoryWatchesOfFailedLookups.get(dirPath);
226+
watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation);
227+
if (watches.mapLocations.size === 0) {
228+
watches.watcher.close();
229+
directoryWatchesOfFailedLookups.delete(dirPath);
227230
}
228231
}
229232

230233
type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void;
231-
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string> | undefined, fn: FailedLookupLocationAction) {
232-
forEach(failedLookupLocations, failedLookupLocation => {
233-
fn(failedLookupLocation, toPath(failedLookupLocation));
234-
});
234+
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string> | undefined, fn: FailedLookupLocationAction, startIndex?: number) {
235+
if (failedLookupLocations) {
236+
for (let i = startIndex || 0; i < failedLookupLocations.length; i++) {
237+
fn(failedLookupLocations[i], toPath(failedLookupLocations[i]));
238+
}
239+
}
235240
}
236241

237242
function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray<string> | undefined, existingFailedLookupLocations: ReadonlyArray<string> | undefined) {
243+
log(`Resolution cache: Updating...`);
244+
const index = existingFailedLookupLocations && failedLookupLocations ?
245+
findDiffIndex(failedLookupLocations, existingFailedLookupLocations) :
246+
0;
247+
238248
// Watch all the failed lookup locations
239-
withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation);
249+
withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation, index);
240250

241251
// Close existing watches for the failed locations
242-
withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher);
252+
withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher, index);
243253
}
244254

245255
function invalidateResolutionCacheOfDeletedFile<T extends NameResolutionWithFailedLookupLocations, R>(
@@ -291,9 +301,15 @@ namespace ts {
291301
invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName);
292302
}
293303

294-
function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) {
295-
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames);
296-
invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives);
304+
function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path) {
305+
const dirPath = getDirectoryPath(fileOrFolder);
306+
const watches = directoryWatchesOfFailedLookups.get(dirPath);
307+
const isFailedLookupFile = watches.mapLocations.has(fileOrFolder);
308+
if (isFailedLookupFile) {
309+
invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames);
310+
invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives);
311+
}
312+
return isFailedLookupFile;
297313
}
298314
}
299315
}

src/compiler/utilities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3543,6 +3543,18 @@ namespace ts {
35433543
}
35443544
}
35453545

3546+
/**
3547+
* Find the index where arrayA and arrayB differ
3548+
*/
3549+
export function findDiffIndex<T>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<T>) {
3550+
for (let i = 0; i < arrayA.length; i++) {
3551+
if (i === arrayB.length || arrayA[i] !== arrayB[i]) {
3552+
return i;
3553+
}
3554+
}
3555+
return arrayA.length;
3556+
}
3557+
35463558
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
35473559
return host.watchFile(file, cb);
35483560
}

src/compiler/watchedProgram.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ namespace ts {
268268
const resolutionCache = createResolutionCache(
269269
fileName => toPath(fileName),
270270
() => compilerOptions,
271-
watchFailedLookupLocation,
271+
watchDirectoryOfFailedLookupLocation,
272272
writeLog
273273
);
274274

@@ -552,14 +552,22 @@ namespace ts {
552552
}
553553
}
554554

555-
function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
556-
return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog);
555+
function watchDirectoryOfFailedLookupLocation(directory: string) {
556+
return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog);
557557
}
558558

559-
function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) {
560-
updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind);
561-
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
562-
scheduleProgramUpdate();
559+
function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) {
560+
const fileOrFolderPath = toPath(fileOrFolder);
561+
562+
if (configFileName) {
563+
// Since the file existance changed, update the sourceFiles cache
564+
(host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
565+
}
566+
567+
// If the location results in update to failed lookup, schedule program update
568+
if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) {
569+
scheduleProgramUpdate();
570+
}
563571
}
564572

565573
function watchMissingFilePath(missingFilePath: Path) {

src/server/editorServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,8 @@ namespace ts.server {
432432
return s => this.logger.info(s + detailedInfo);
433433
}
434434

435-
toPath(fileName: string, basePath = this.currentDirectory) {
436-
return toPath(fileName, basePath, this.toCanonicalFileName);
435+
toPath(fileName: string) {
436+
return toPath(fileName, this.currentDirectory, this.toCanonicalFileName);
437437
}
438438

439439
/* @internal */

src/server/project.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ namespace ts.server {
216216
this.resolutionCache = createResolutionCache(
217217
fileName => this.projectService.toPath(fileName),
218218
() => this.compilerOptions,
219-
(failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath),
219+
directory => this.watchDirectoryOfFailedLookup(directory),
220220
s => this.projectService.logger.info(s),
221221
this.getProjectName(),
222222
() => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined
@@ -233,24 +233,30 @@ namespace ts.server {
233233
this.markAsDirty();
234234
}
235235

236-
private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) {
237-
return this.projectService.watchFile(
236+
private watchDirectoryOfFailedLookup(directory: string) {
237+
return this.projectService.watchDirectory(
238238
this.projectService.host,
239-
failedLookupLocation,
240-
(fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath),
239+
directory,
240+
fileOrFolder => this.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder),
241+
WatchDirectoryFlags.None,
241242
WatchType.FailedLookupLocation,
242243
this
243244
);
244245
}
245246

246-
private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) {
247+
private onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) {
248+
const fileOrFolderPath = this.projectService.toPath(fileOrFolder);
249+
247250
// There is some kind of change in the failed lookup location, update the program
248251
if (this.projectKind === ProjectKind.Configured) {
249-
(this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind);
252+
(this.lsHost.host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
253+
}
254+
255+
// If the location results in update to failed lookup, schedule program update
256+
if (this.resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) {
257+
this.markAsDirty();
258+
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
250259
}
251-
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath);
252-
this.markAsDirty();
253-
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
254260
}
255261

256262
private setInternalCompilerOptionsForEmittingJsFiles() {

0 commit comments

Comments
 (0)