Skip to content

Use cache for the non-relative module resolution and enhance the watches for failed lookup locations #22136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3296,7 +3296,7 @@
"category": "Message",
"code": 6146
},
"Resolution for module '{0}' was found in cache.": {
"Resolution for module '{0}' was found in cache from location '{1}'.": {
"category": "Message",
"code": 6147
},
Expand Down
20 changes: 16 additions & 4 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,20 @@ namespace ts {
}

export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache {
const directoryToModuleNameMap = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
const moduleNameToDirectoryMap = createMap<PerModuleNameCache>();
return createModuleResolutionCacheWithMaps(
createMap<Map<ResolvedModuleWithFailedLookupLocations>>(),
createMap<PerModuleNameCache>(),
currentDirectory,
getCanonicalFileName
);
}

/*@internal*/
export function createModuleResolutionCacheWithMaps(
directoryToModuleNameMap: Map<Map<ResolvedModuleWithFailedLookupLocations>>,
moduleNameToDirectoryMap: Map<PerModuleNameCache>,
currentDirectory: string,
getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache {

return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName };

Expand Down Expand Up @@ -445,7 +457,7 @@ namespace ts {

if (result) {
if (traceEnabled) {
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName);
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
}
}
else {
Expand Down Expand Up @@ -1187,7 +1199,7 @@ namespace ts {
const result = cache && cache.get(containingDirectory);
if (result) {
if (traceEnabled) {
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName);
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
}
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
}
Expand Down
152 changes: 87 additions & 65 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace ts {
interface ResolutionWithFailedLookupLocations {
readonly failedLookupLocations: ReadonlyArray<string>;
isInvalidated?: boolean;
refCount?: number;
}

interface ResolutionWithResolvedFileName {
Expand All @@ -42,6 +43,7 @@ namespace ts {

export interface ResolutionCacheHost extends ModuleResolutionHost {
toPath(fileName: string): Path;
getCanonicalFileName: GetCanonicalFileName;
getCompilationSettings(): CompilerOptions;
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
onInvalidatedResolution(): void;
Expand Down Expand Up @@ -78,18 +80,25 @@ namespace ts {
let filesWithInvalidatedResolutions: Map<true> | undefined;
let allFilesHaveInvalidatedResolution = false;

const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();

// The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
// The key in the map is source file's path.
// The values are Map of resolutions with key being name lookedup.
const resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
const perDirectoryResolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
const nonRelaticeModuleNameCache = createMap<PerModuleNameCache>();
const moduleResolutionCache = createModuleResolutionCacheWithMaps(
perDirectoryResolvedModuleNames,
nonRelaticeModuleNameCache,
getCurrentDirectory(),
resolutionHost.getCanonicalFileName
);

const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();

const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();

/**
* These are the extensions that failed lookup files will have by default,
* any other extension of failed lookup will be store that path in custom failed lookup path
Expand Down Expand Up @@ -173,6 +182,7 @@ namespace ts {

function clearPerDirectoryResolutions() {
perDirectoryResolvedModuleNames.clear();
nonRelaticeModuleNameCache.clear();
perDirectoryResolvedTypeReferenceDirectives.clear();
}

Expand All @@ -189,7 +199,7 @@ namespace ts {
}

function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host);
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache);
// return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
if (!resolutionHost.getGlobalCache) {
return primaryResult;
Expand Down Expand Up @@ -248,17 +258,11 @@ namespace ts {
perDirectoryResolution.set(name, resolution);
}
resolutionsInFile.set(name, resolution);
if (resolution.failedLookupLocations) {
if (existingResolution && existingResolution.failedLookupLocations) {
watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution);
}
else {
watchFailedLookupLocationOfResolution(resolution, 0);
}
}
else if (existingResolution) {
watchFailedLookupLocationOfResolution(resolution);
if (existingResolution) {
stopWatchFailedLookupLocationOfResolution(existingResolution);
}

if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
filesWithChangedSetOfUnresolvedImports.push(path);
// reset log changes to avoid recording the same file multiple times
Expand Down Expand Up @@ -390,80 +394,98 @@ namespace ts {
return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
}

function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) {
const failedLookupLocations = resolution.failedLookupLocations;
const existingFailedLookupLocations = existingResolution.failedLookupLocations;
for (let index = 0; index < failedLookupLocations.length; index++) {
if (index === existingFailedLookupLocations.length) {
// Additional failed lookup locations, watch from this index
watchFailedLookupLocationOfResolution(resolution, index);
return;
}
else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) {
// Different failed lookup locations,
// Watch new resolution failed lookup locations from this index and
// stop watching existing resolutions from this index
watchFailedLookupLocationOfResolution(resolution, index);
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index);
return;
}
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
// No need to set the resolution refCount
if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) {
return;
}

// All new failed lookup locations are already watched (and are same),
// Stop watching failed lookup locations of existing resolution after failed lookup locations length
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length);
}
if (resolution.refCount !== undefined) {
resolution.refCount++;
return;
}

function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
for (let i = startIndex; i < failedLookupLocations.length; i++) {
const failedLookupLocation = failedLookupLocations[i];
resolution.refCount = 1;
const { failedLookupLocations } = resolution;
let setAtRoot = false;
for (const failedLookupLocation of failedLookupLocations) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
// If the failed lookup location path is not one of the supported extensions,
// store it in the custom path
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
}
const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
if (!ignore) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
if (dirWatcher) {
dirWatcher.refCount++;
// If the failed lookup location path is not one of the supported extensions,
// store it in the custom path
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
}
if (dirPath === rootPath) {
setAtRoot = true;
}
else {
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
setDirectoryWatcher(dir, dirPath);
}
}
}

if (setAtRoot) {
setDirectoryWatcher(rootDir, rootPath);
}
}

function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
if (resolution.failedLookupLocations) {
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
function setDirectoryWatcher(dir: string, dirPath: Path) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
if (dirWatcher) {
dirWatcher.refCount++;
}
else {
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
}
}

function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
for (let i = startIndex; i < failedLookupLocations.length; i++) {
const failedLookupLocation = failedLookupLocations[i];
function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
if (!resolution.failedLookupLocations || !resolution.failedLookupLocations.length) {
return;
}

resolution.refCount!--;
if (resolution.refCount) {
return;
}

const { failedLookupLocations } = resolution;
let removeAtRoot = false;
for (const failedLookupLocation of failedLookupLocations) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
if (refCount) {
if (refCount === 1) {
customFailedLookupPaths.delete(failedLookupLocationPath);
const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
if (!ignore) {
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
if (refCount) {
if (refCount === 1) {
customFailedLookupPaths.delete(failedLookupLocationPath);
}
else {
Debug.assert(refCount > 1);
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
}
}

if (dirPath === rootPath) {
removeAtRoot = true;
}
else {
Debug.assert(refCount > 1);
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
removeDirectoryWatcher(dirPath);
}
}
const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
if (!ignore) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
// Do not close the watcher yet since it might be needed by other failed lookup locations.
dirWatcher.refCount--;
}
}
if (removeAtRoot) {
removeDirectoryWatcher(rootPath);
}
}

function removeDirectoryWatcher(dirPath: string) {
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
// Do not close the watcher yet since it might be needed by other failed lookup locations.
dirWatcher.refCount--;
}

function createDirectoryWatcher(directory: string, dirPath: Path) {
Expand Down
Loading