Skip to content

Commit b3ab401

Browse files
committed
Handle package json lifetime
- All the package json watched are ref counted if watched - Not watched package json locations are released - First of this kind are when resolution from global cache fails, we dont watch those locations so not safe to have them cached - If we are looking for a file and the file is not found, the package json locations looked up are not watched
1 parent a51e79a commit b3ab401

37 files changed

Lines changed: 229 additions & 91 deletions

File tree

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3791,6 +3791,9 @@ export function createProgram(_rootNamesOrOptions: readonly string[] | CreatePro
37913791
}
37923792
(filesWithReferencesProcessed ??= new Set()).add(file.path);
37933793
}
3794+
else {
3795+
host.onSourceFileNotCreated?.(sourceFileOptions);
3796+
}
37943797
return file;
37953798
}
37963799

src/compiler/resolutionCache.ts

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CompilerOptions,
88
createModeAwareCache,
99
createModuleResolutionCache,
10+
CreateSourceFileOptions,
1011
createTypeReferenceDirectiveResolutionCache,
1112
createTypeReferenceResolutionLoader,
1213
Debug,
@@ -53,7 +54,6 @@ import {
5354
noopFileWatcher,
5455
normalizePath,
5556
packageIdToString,
56-
PackageJsonInfoCacheEntry,
5757
parseNodeModuleFromPath,
5858
Path,
5959
PathPathComponents,
@@ -111,6 +111,7 @@ export interface ResolutionCache extends Required<CompilerHostSupportingResoluti
111111
resolvedFileToResolution: Map<Path, Set<ResolutionWithFailedLookupLocations>>;
112112
resolutionsWithFailedLookups: Set<ResolutionWithFailedLookupLocations>;
113113
resolutionsWithOnlyAffectingLocations: Set<ResolutionWithFailedLookupLocations>;
114+
packageJsonRefCount: Map<Path, number>;
114115
directoryWatchesOfFailedLookups: Map<Path, DirectoryWatchesOfFailedLookup>;
115116
fileWatchesOfAffectingLocations: Map<string, FileWatcherOfAffectingLocation>;
116117
packageDirWatchers: Map<Path, PackageDirWatcher>;
@@ -693,6 +694,8 @@ export function createResolutionCache(
693694
let resolutionsResolvedWithGlobalCache = 0;
694695
let resolutionsResolvedWithoutGlobalCache = 0;
695696

697+
const packageJsonRefCount = new Map<Path, number>();
698+
let potentiallyUnwatchedPackageJsons: Set<Path> | undefined;
696699
const directoryWatchesOfFailedLookups = new Map<Path, DirectoryWatchesOfFailedLookup>();
697700
const fileWatchesOfAffectingLocations = new Map<string, FileWatcherOfAffectingLocation>();
698701
const rootDir = getRootDirectoryOfResolutionCache(rootDirForResolution, getCurrentDirectory);
@@ -718,6 +721,7 @@ export function createResolutionCache(
718721
resolvedFileToResolution,
719722
resolutionsWithFailedLookups,
720723
resolutionsWithOnlyAffectingLocations,
724+
packageJsonRefCount,
721725
directoryWatchesOfFailedLookups,
722726
fileWatchesOfAffectingLocations,
723727
packageDirWatchers,
@@ -734,6 +738,7 @@ export function createResolutionCache(
734738
resolveTypeReferenceDirectiveReferences,
735739
onReusedModuleResolutions,
736740
onReusedTypeReferenceDirectiveResolutions,
741+
onSourceFileNotCreated,
737742
resolveLibrary,
738743
resolveSingleModuleNameWithoutWatching,
739744
removeResolutionsFromProjectReferenceRedirects,
@@ -754,9 +759,11 @@ export function createResolutionCache(
754759
function clear() {
755760
potentiallyUnreferencedResolutions = undefined;
756761
potentiallyUnreferencedDirWatchers = undefined;
762+
potentiallyUnwatchedPackageJsons = undefined;
757763
newUnresolvedResolutionCachePassResolutions = undefined;
758764
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
759765
clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf);
766+
packageJsonRefCount.clear();
760767
isSymlinkCache.clear();
761768
packageDirWatchers.clear();
762769
dirPathToSymlinkPackageRefCount.clear();
@@ -890,6 +897,8 @@ export function createResolutionCache(
890897
potentiallyUnreferencedResolutions = undefined;
891898
}
892899
hasChangedAutomaticTypeDirectiveNames = false;
900+
potentiallyUnwatchedPackageJsons?.forEach(releasePotentiallyUnwatchedPackageJson);
901+
potentiallyUnwatchedPackageJsons = undefined;
893902
if (!skipCacheCompact) compactCaches(newProgram);
894903
moduleResolutionCache.isReadonly = true;
895904
typeReferenceDirectiveResolutionCache.isReadonly = true;
@@ -954,13 +963,37 @@ export function createResolutionCache(
954963
}
955964
}
956965

966+
function releasePotentiallyUnwatchedPackageJson(path: Path) {
967+
if (!packageJsonRefCount.has(path)) moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(path);
968+
}
969+
970+
function releasePackageJsonCachePath(path: Path) {
971+
moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(path);
972+
packageJsonRefCount.delete(path);
973+
}
974+
975+
function releasePackageJson(path: Path) {
976+
const existing = packageJsonRefCount.get(path)!;
977+
if (existing !== 1) packageJsonRefCount.set(path, existing - 1);
978+
else releasePackageJsonCachePath(path);
979+
}
980+
981+
function addRefToPackageJson(path: Path) {
982+
packageJsonRefCount.set(path, (packageJsonRefCount.get(path) ?? 0) + 1);
983+
}
984+
957985
function closeFileWatcherOfAffectingLocation(watcher: FileWatcherOfAffectingLocation, path: string) {
958986
if (watcher.files === 0 && watcher.resolutions === 0 && !watcher.symlinks?.size) {
959987
fileWatchesOfAffectingLocations.delete(path);
988+
releasePackageJson(resolutionHost.toPath(path));
960989
watcher.watcher.close();
961990
}
962991
}
963992

993+
function onSourceFileNotCreated(sourceFileOptions: CreateSourceFileOptions) {
994+
sourceFileOptions.packageJsonLocations?.forEach(addToPotentiallyUnwatchedPackageJsons);
995+
}
996+
964997
function getValidResolution<T extends ResolutionWithFailedLookupLocations>(resolution: T | undefined) {
965998
return isInvalidatedResolution(resolution) ? undefined : resolution;
966999
}
@@ -1301,6 +1334,14 @@ export function createResolutionCache(
13011334
watchFailedLookupLocationOfResolution(resolution);
13021335
watchAffectingLocationsOfResolution(resolution);
13031336
if (!firstTime) return;
1337+
if (resolution.globalCacheResolution && !resolution.globalCacheResolution.resolution.resolvedModule) {
1338+
// Add to potentially unreferenced resolutions
1339+
resolution.globalCacheResolution.resolution.failedLookupLocations?.forEach(
1340+
addToPotentiallyUnwatchedPackageJsonsIfPackageJson,
1341+
);
1342+
if (resolution.globalCacheResolution.resolution.alternateResult) addToPotentiallyUnwatchedPackageJsonsIfPackageJson(resolution.globalCacheResolution.resolution.alternateResult);
1343+
resolution.globalCacheResolution.resolution.affectingLocations?.forEach(addToPotentiallyUnwatchedPackageJsons);
1344+
}
13041345
if (isResolvedWithGlobalCachePass(resolution)) resolutionsResolvedWithGlobalCache++;
13051346
else if (isResolvedWithoutGlobalCachePass(resolution)) resolutionsResolvedWithoutGlobalCache++;
13061347
const resolved = getResolutionWithResolvedFileName(resolution);
@@ -1312,8 +1353,17 @@ export function createResolutionCache(
13121353
}
13131354
}
13141355

1356+
function addToPotentiallyUnwatchedPackageJsonsIfPackageJson(location: string) {
1357+
if (endsWith(location, "/package.json")) addToPotentiallyUnwatchedPackageJsons(location);
1358+
}
1359+
1360+
function addToPotentiallyUnwatchedPackageJsons(location: string) {
1361+
(potentiallyUnwatchedPackageJsons ??= new Set()).add(resolutionHost.toPath(location));
1362+
}
1363+
13151364
function watchFailedLookupLocation(failedLookupLocation: string) {
13161365
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
1366+
if (endsWith(failedLookupLocationPath, "/package.json")) addRefToPackageJson(failedLookupLocationPath);
13171367
const toWatch = getDirectoryToWatchFailedLookupLocation(
13181368
failedLookupLocation,
13191369
failedLookupLocationPath,
@@ -1398,14 +1448,15 @@ export function createResolutionCache(
13981448
watcher: canWatchAffectingLocation(resolutionHost.toPath(locationToWatch)) ?
13991449
resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => {
14001450
cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind);
1401-
invalidateAffectingFileWatcher(locationToWatch, moduleResolutionCache.getPackageJsonInfoCache().getInternalMap());
1451+
invalidateAffectingFileWatcher(locationToWatch);
14021452
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
14031453
}) : noopFileWatcher,
14041454
resolutions: isSymlink ? 0 : resolutions,
14051455
files: isSymlink ? 0 : files,
14061456
symlinks: undefined,
14071457
};
14081458
fileWatchesOfAffectingLocations.set(locationToWatch, watcher);
1459+
addRefToPackageJson(resolutionHost.toPath(locationToWatch));
14091460
if (isSymlink) symlinkWatcher = watcher;
14101461
}
14111462
if (isSymlink) {
@@ -1417,6 +1468,7 @@ export function createResolutionCache(
14171468
// Close symlink watcher if no ref
14181469
if (symlinkWatcher?.symlinks?.delete(affectingLocation) && !symlinkWatcher.symlinks.size && !symlinkWatcher.resolutions && !symlinkWatcher.files) {
14191470
fileWatchesOfAffectingLocations.delete(locationToWatch);
1471+
releasePackageJson(resolutionHost.toPath(locationToWatch));
14201472
symlinkWatcher.watcher.close();
14211473
}
14221474
},
@@ -1426,16 +1478,17 @@ export function createResolutionCache(
14261478
symlinks: undefined,
14271479
};
14281480
fileWatchesOfAffectingLocations.set(affectingLocation, watcher);
1481+
addRefToPackageJson(resolutionHost.toPath(affectingLocation));
14291482
(symlinkWatcher.symlinks ??= new Set()).add(affectingLocation);
14301483
}
14311484
}
14321485

1433-
function invalidateAffectingFileWatcher(path: string, packageJsonMap: Map<Path, PackageJsonInfoCacheEntry> | undefined) {
1486+
function invalidateAffectingFileWatcher(path: string) {
14341487
const watcher = fileWatchesOfAffectingLocations.get(path);
14351488
if (watcher?.resolutions) (affectingPathChecks ??= new Set()).add(path);
14361489
if (watcher?.files) (affectingPathChecksForFile ??= new Set()).add(path);
1437-
watcher?.symlinks?.forEach(path => invalidateAffectingFileWatcher(path, packageJsonMap));
1438-
packageJsonMap?.delete(resolutionHost.toPath(path));
1490+
moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(resolutionHost.toPath(path));
1491+
watcher?.symlinks?.forEach(path => invalidateAffectingFileWatcher(path));
14391492
}
14401493

14411494
function createDirectoryWatcherForPackageDir(
@@ -1523,6 +1576,7 @@ export function createResolutionCache(
15231576

15241577
function stopWatchFailedLookupLocation(failedLookupLocation: string) {
15251578
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
1579+
if (endsWith(failedLookupLocationPath, "/package.json")) releasePackageJson(failedLookupLocationPath);
15261580
const toWatch = getDirectoryToWatchFailedLookupLocation(
15271581
failedLookupLocation,
15281582
failedLookupLocationPath,

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8199,6 +8199,7 @@ export interface CompilerHostSupportingResolutionCache {
81998199
redirectedReference: ResolvedProjectReference | undefined,
82008200
options: CompilerOptions,
82018201
): void;
8202+
onSourceFileNotCreated?(sourceFileOptions: CreateSourceFileOptions): void;
82028203
}
82038204
/** @internal */
82048205
export interface CompilerHost extends CompilerHostSupportingResolutionCache {

src/compiler/watchPublic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
524524
getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
525525
currentDirectory,
526526
);
527+
compilerHost.onSourceFileNotCreated = resolutionCache.onSourceFileNotCreated.bind(resolutionCache);
527528
// Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names
528529
compilerHost.resolveModuleNameLiterals = maybeBind(host, host.resolveModuleNameLiterals);
529530
compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames);

src/harness/incrementalUtils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ export function verifyResolutionCache(
435435

436436
// Verify that caches are same:
437437
forEachModuleOrTypeRefOrLibCache((cache, cacheType) => verifyModuleOrTypeResolutionCache(cache, actual[cacheType], cacheType));
438+
verifyPackageJsonWatchInfo();
438439

439440
// Stop watching resolutions to verify everything gets closed.
440441
expected.startCachingPerDirectoryResolution();
@@ -453,6 +454,11 @@ export function verifyResolutionCache(
453454
ts.Debug.assert(expected.countResolutionsResolvedWithGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithGlobalCache should be cleared`);
454455
ts.Debug.assert(expected.countResolutionsResolvedWithoutGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithoutGlobalCache should be cleared`);
455456
forEachModuleOrTypeRefOrLibCache((cache, cacheType) => verifyModuleOrTypeResolutionCacheIsEmpty(cache, cacheType, /*compacted*/ false));
457+
ts.Debug.assert(expected.packageJsonRefCount.size === 0, `${projectName}:: packageJsonRefCount should be cleared`);
458+
ts.Debug.assert(
459+
expected.moduleResolutionCache.getPackageJsonInfoCache().getInternalMap() === undefined || expected.moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()!.size === 0,
460+
`${projectName}:: Shouldnt have any packageJson entries`,
461+
);
456462

457463
expected.compactCaches(/*newProgram*/ undefined);
458464
forEachModuleOrTypeRefOrLibCache((cache, cacheType) => verifyModuleOrTypeResolutionCacheIsEmpty(cache, cacheType, /*compacted*/ true));
@@ -803,6 +809,32 @@ export function verifyResolutionCache(
803809
}
804810
});
805811
}
812+
813+
function verifyPackageJsonWatchInfo() {
814+
verifyMap(
815+
expected.packageJsonRefCount,
816+
actual.packageJsonRefCount,
817+
(expectedRefCount, actualRefCount, caption) =>
818+
ts.Debug.assert(
819+
expectedRefCount === actualRefCount,
820+
`${projectName}:: ${caption}`,
821+
),
822+
"packageJsonRefCount",
823+
);
824+
verifyMap(
825+
actual.packageJsonRefCount,
826+
actual.moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(),
827+
(refCount, packageJsonCacheEntry, caption) => {
828+
// Ok to have refcount on entry not in cache
829+
if (!packageJsonCacheEntry) return;
830+
ts.Debug.assert(
831+
!!refCount,
832+
`${projectName}:: ${caption}:: expected ref count on packageJson as there is entry in the cache`,
833+
);
834+
},
835+
"Package Json cache watched",
836+
);
837+
}
806838
}
807839

808840
function verifyMap<Key extends string, Expected, Actual>(

src/server/project.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
createCacheableExportInfoMap,
2525
createLanguageService,
2626
createResolutionCache,
27+
CreateSourceFileOptions,
2728
createSymlinkCache,
2829
Debug,
2930
Diagnostic,
@@ -884,6 +885,11 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
884885
return this.resolutionCache.resolveLibrary(libraryName, resolveFrom, options, libFileName);
885886
}
886887

888+
/** @internal */
889+
onSourceFileNotCreated(sourceFileOptions: CreateSourceFileOptions): void {
890+
this.resolutionCache.onSourceFileNotCreated(sourceFileOptions);
891+
}
892+
887893
directoryExists(path: string): boolean {
888894
return this.directoryStructureHost.directoryExists!(path); // TODO: GH#18217
889895
}

src/services/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,7 @@ export function createLanguageService(
17731773
onReusedModuleResolutions: maybeBind(host, host.onReusedModuleResolutions),
17741774
onReusedTypeReferenceDirectiveResolutions: maybeBind(host, host.onReusedTypeReferenceDirectiveResolutions),
17751775
resolveLibrary: maybeBind(host, host.resolveLibrary),
1776+
onSourceFileNotCreated: maybeBind(host, host.onSourceFileNotCreated),
17761777
useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
17771778
getParsedCommandLine,
17781779
jsDocParsingMode: host.jsDocParsingMode,

tests/baselines/reference/tscWatch/libraryResolution/with-config.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,13 +1984,13 @@ File '/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts'
19841984
Resolving real path for '/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts', result '/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts'.
19851985
======== Module name '@typescript/lib-dom' was successfully resolved to '/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts'. ========
19861986
File '/home/src/workspace/projects/node_modules/@typescript/lib-dom/package.json' does not exist according to earlier cached lookups.
1987-
File '/home/src/workspace/projects/node_modules/@typescript/package.json' does not exist according to earlier cached lookups.
1988-
File '/home/src/workspace/projects/node_modules/package.json' does not exist according to earlier cached lookups.
1989-
File '/home/src/workspace/projects/package.json' does not exist according to earlier cached lookups.
1990-
File '/home/src/workspace/package.json' does not exist according to earlier cached lookups.
1991-
File '/home/src/package.json' does not exist according to earlier cached lookups.
1992-
File '/home/package.json' does not exist according to earlier cached lookups.
1993-
File '/package.json' does not exist according to earlier cached lookups.
1987+
File '/home/src/workspace/projects/node_modules/@typescript/package.json' does not exist.
1988+
File '/home/src/workspace/projects/node_modules/package.json' does not exist.
1989+
File '/home/src/workspace/projects/package.json' does not exist.
1990+
File '/home/src/workspace/package.json' does not exist.
1991+
File '/home/src/package.json' does not exist.
1992+
File '/home/package.json' does not exist.
1993+
File '/package.json' does not exist.
19941994
FileWatcher:: Added:: WatchInfo: /home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts 250 undefined Source file
19951995
FileWatcher:: Close:: WatchInfo: /home/src/tslibs/TS/Lib/lib.dom.d.ts 250 undefined Source file
19961996
FileWatcher:: Added:: WatchInfo: /home/src/workspace/projects/node_modules/@typescript/lib-dom/package.json 2000 undefined File location affecting resolution

tests/baselines/reference/tscWatch/libraryResolution/without-config.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,13 +1108,13 @@ File '/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.
11081108
Resolving real path for '/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts', result '/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts'.
11091109
======== Module name '@typescript/lib-webworker' was successfully resolved to '/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts'. ========
11101110
File '/home/src/workspace/projects/node_modules/@typescript/lib-webworker/package.json' does not exist according to earlier cached lookups.
1111-
File '/home/src/workspace/projects/node_modules/@typescript/package.json' does not exist according to earlier cached lookups.
1112-
File '/home/src/workspace/projects/node_modules/package.json' does not exist according to earlier cached lookups.
1113-
File '/home/src/workspace/projects/package.json' does not exist according to earlier cached lookups.
1114-
File '/home/src/workspace/package.json' does not exist according to earlier cached lookups.
1115-
File '/home/src/package.json' does not exist according to earlier cached lookups.
1116-
File '/home/package.json' does not exist according to earlier cached lookups.
1117-
File '/package.json' does not exist according to earlier cached lookups.
1111+
File '/home/src/workspace/projects/node_modules/@typescript/package.json' does not exist.
1112+
File '/home/src/workspace/projects/node_modules/package.json' does not exist.
1113+
File '/home/src/workspace/projects/package.json' does not exist.
1114+
File '/home/src/workspace/package.json' does not exist.
1115+
File '/home/src/package.json' does not exist.
1116+
File '/home/package.json' does not exist.
1117+
File '/package.json' does not exist.
11181118
FileWatcher:: Added:: WatchInfo: /home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts 250 undefined Source file
11191119
Reusing resolution of module '@typescript/lib-scripthost' from '/home/src/workspace/projects/__lib_node_modules_lookup_lib.scripthost.d.ts__.ts' of old program, it was not resolved.
11201120
Reusing resolution of module '@typescript/lib-es5' from '/home/src/workspace/projects/__lib_node_modules_lookup_lib.es5.d.ts__.ts' of old program, it was not resolved.

0 commit comments

Comments
 (0)