Skip to content

Commit d7269f1

Browse files
authored
Merge pull request #19053 from Microsoft/resolutionCacheDefensiveChecks
Resolution cache defensive checks
2 parents edf0a95 + 55bbcff commit d7269f1

File tree

5 files changed

+41
-25
lines changed

5 files changed

+41
-25
lines changed

src/compiler/resolutionCache.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ namespace ts {
141141
resolvedModuleNames.clear();
142142
resolvedTypeReferenceDirectives.clear();
143143
allFilesHaveInvalidatedResolution = false;
144-
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
144+
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
145+
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
146+
perDirectoryResolvedModuleNames.clear();
147+
perDirectoryResolvedTypeReferenceDirectives.clear();
145148
}
146149

147150
function startRecordingFilesWithChangedResolutions() {
@@ -166,7 +169,10 @@ namespace ts {
166169
}
167170

168171
function startCachingPerDirectoryResolution() {
169-
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
172+
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
173+
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
174+
perDirectoryResolvedModuleNames.clear();
175+
perDirectoryResolvedTypeReferenceDirectives.clear();
170176
}
171177

172178
function finishCachingPerDirectoryResolution() {

src/compiler/watch.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,21 +322,21 @@ namespace ts {
322322

323323
if (hasChangedCompilerOptions) {
324324
newLine = getNewLineCharacter(compilerOptions, system);
325+
if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) {
326+
resolutionCache.clear();
327+
}
325328
}
326329

327330
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
328331
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) {
329332
return;
330333
}
331334

332-
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
333-
resolutionCache.clear();
334-
}
335-
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
336-
hasChangedCompilerOptions = false;
337335
beforeCompile(compilerOptions);
338336

339337
// Compile the program
338+
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
339+
hasChangedCompilerOptions = false;
340340
resolutionCache.startCachingPerDirectoryResolution();
341341
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
342342
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;

src/server/editorServices.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,9 @@ namespace ts.server {
842842
this.logger.info(`remove project: ${project.getRootFiles().toString()}`);
843843

844844
project.close();
845+
if (Debug.shouldAssert(AssertionLevel.Normal)) {
846+
this.filenameToScriptInfo.forEach(info => Debug.assert(!info.isAttached(project)));
847+
}
845848
// Remove the project from pending project updates
846849
this.pendingProjectUpdates.delete(project.getProjectName());
847850

src/server/project.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,9 @@ namespace ts.server {
233233
this.realpath = path => host.realpath(path);
234234
}
235235

236-
this.languageService = createLanguageService(this, this.documentRegistry);
237236
// Use the current directory as resolution root only if the project created using current directory string
238237
this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory);
238+
this.languageService = createLanguageService(this, this.documentRegistry);
239239
if (!languageServiceEnabled) {
240240
this.disableLanguageService();
241241
}
@@ -498,25 +498,23 @@ namespace ts.server {
498498

499499
close() {
500500
if (this.program) {
501-
// if we have a program - release all files that are enlisted in program
501+
// if we have a program - release all files that are enlisted in program but arent root
502+
// The releasing of the roots happens later
503+
// The project could have pending update remaining and hence the info could be in the files but not in program graph
502504
for (const f of this.program.getSourceFiles()) {
503-
const info = this.projectService.getScriptInfo(f.fileName);
504-
// We might not find the script info in case its not associated with the project any more
505-
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
506-
if (info) {
507-
info.detachFromProject(this);
508-
}
505+
this.detachScriptInfoIfNotRoot(f.fileName);
509506
}
510507
}
511-
if (!this.program || !this.languageServiceEnabled) {
512-
// release all root files either if there is no program or language service is disabled.
513-
// in the latter case set of root files can be larger than the set of files in program.
514-
for (const root of this.rootFiles) {
515-
root.detachFromProject(this);
516-
}
508+
// Release external files
509+
forEach(this.externalFiles, externalFile => this.detachScriptInfoIfNotRoot(externalFile));
510+
// Always remove root files from the project
511+
for (const root of this.rootFiles) {
512+
root.detachFromProject(this);
517513
}
514+
518515
this.rootFiles = undefined;
519516
this.rootFilesMap = undefined;
517+
this.externalFiles = undefined;
520518
this.program = undefined;
521519
this.builder = undefined;
522520
this.resolutionCache.clear();
@@ -535,6 +533,15 @@ namespace ts.server {
535533
this.languageService = undefined;
536534
}
537535

536+
private detachScriptInfoIfNotRoot(uncheckedFilename: string) {
537+
const info = this.projectService.getScriptInfo(uncheckedFilename);
538+
// We might not find the script info in case its not associated with the project any more
539+
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
540+
if (info && !this.isRoot(info)) {
541+
info.detachFromProject(this);
542+
}
543+
}
544+
538545
isClosed() {
539546
return this.rootFiles === undefined;
540547
}
@@ -735,7 +742,6 @@ namespace ts.server {
735742
*/
736743
updateGraph(): boolean {
737744
this.resolutionCache.startRecordingFilesWithChangedResolutions();
738-
this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
739745

740746
let hasChanges = this.updateGraphWorker();
741747

@@ -795,9 +801,10 @@ namespace ts.server {
795801

796802
private updateGraphWorker() {
797803
const oldProgram = this.program;
798-
804+
Debug.assert(!this.isClosed(), "Called update graph worker of closed project");
799805
this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`);
800806
const start = timestamp();
807+
this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
801808
this.resolutionCache.startCachingPerDirectoryResolution();
802809
this.program = this.languageService.getProgram();
803810
this.resolutionCache.finishCachingPerDirectoryResolution();
@@ -1320,14 +1327,13 @@ namespace ts.server {
13201327
}
13211328

13221329
close() {
1323-
super.close();
1324-
13251330
if (this.configFileWatcher) {
13261331
this.configFileWatcher.close();
13271332
this.configFileWatcher = undefined;
13281333
}
13291334

13301335
this.stopWatchingWildCards();
1336+
super.close();
13311337
}
13321338

13331339
addOpenRef() {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7145,6 +7145,7 @@ declare namespace ts.server {
71457145
getExternalFiles(): SortedReadonlyArray<string>;
71467146
getSourceFile(path: Path): SourceFile;
71477147
close(): void;
7148+
private detachScriptInfoIfNotRoot(uncheckedFilename);
71487149
isClosed(): boolean;
71497150
hasRoots(): boolean;
71507151
getRootFiles(): NormalizedPath[];

0 commit comments

Comments
 (0)