mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 00:21:05 -05:00
perf(lsp): Don't retain SourceFileObject
s in sourceFileCache
longer than necessary (#23258)
The TS language service requests source files via [getSourceFile](7a25fd5ef0/cli/tsc/99_main_compiler.js (L560)
). In that function, we [unconditionally add](7a25fd5ef0/cli/tsc/99_main_compiler.js (L613-L614)
) the source file to our sourceFileCache. The issue is that we only remove things from that cache if the source file [becomes out of date](7a25fd5ef0/cli/tsc/99_main_compiler.js (L777-L783)
). For files that don't get changed, we keep them in the cache indefinitely. So sometimes we keep SourceFile objects from being GC'ed because they're retained in our cache, even though TS doesn't refer to them any more. I see this in pretty much all of the heap snapshots I've taken. --- The fix here is pretty direct - just store weak references to the sourcefiles in the cache. It doesn't really change our caching behavior, it just prevents us from being the only retainer of a `SourceFile`. I also split the `sourceFileCache` into a separate cache just for assets, as we rely on those being alive. The simpler fix is to only cache assets, but presumably that has a perf impact. --- In local testing, this PR reduced the size of the JS heap by about 1 GB when using `deno lsp` in the Typescript repo.
This commit is contained in:
parent
f9f37963b7
commit
b74a4f29f2
1 changed files with 29 additions and 14 deletions
|
@ -147,8 +147,15 @@ delete Object.prototype.__proto__;
|
|||
}
|
||||
}
|
||||
|
||||
// In the case of the LSP, this will only ever contain the assets.
|
||||
// Cache of asset source files.
|
||||
/** @type {Map<string, ts.SourceFile>} */
|
||||
const assetSourceFileCache = new Map();
|
||||
|
||||
// Cache of source files, keyed by specifier.
|
||||
// Stores weak references to ensure we aren't
|
||||
// retaining `ts.SourceFile` objects longer than needed.
|
||||
// This should not include assets, which have a separate cache.
|
||||
/** @type {Map<string, WeakRef<ts.SourceFile>>} */
|
||||
const sourceFileCache = new Map();
|
||||
|
||||
/** @type {string[]=} */
|
||||
|
@ -576,7 +583,11 @@ delete Object.prototype.__proto__;
|
|||
// Needs the original specifier
|
||||
specifier = normalizedToOriginalMap.get(specifier) ?? specifier;
|
||||
|
||||
let sourceFile = sourceFileCache.get(specifier);
|
||||
const isAsset = specifier.startsWith(ASSETS_URL_PREFIX);
|
||||
|
||||
let sourceFile = isAsset
|
||||
? assetSourceFileCache.get(specifier)
|
||||
: sourceFileCache.get(specifier)?.deref();
|
||||
if (sourceFile) {
|
||||
return sourceFile;
|
||||
}
|
||||
|
@ -607,10 +618,12 @@ delete Object.prototype.__proto__;
|
|||
);
|
||||
sourceFile.moduleName = specifier;
|
||||
sourceFile.version = version;
|
||||
if (specifier.startsWith(ASSETS_URL_PREFIX)) {
|
||||
if (isAsset) {
|
||||
sourceFile.version = "1";
|
||||
assetSourceFileCache.set(specifier, sourceFile);
|
||||
} else {
|
||||
sourceFileCache.set(specifier, new WeakRef(sourceFile));
|
||||
}
|
||||
sourceFileCache.set(specifier, sourceFile);
|
||||
scriptVersionCache.set(specifier, version);
|
||||
return sourceFile;
|
||||
},
|
||||
|
@ -773,9 +786,12 @@ delete Object.prototype.__proto__;
|
|||
if (logDebug) {
|
||||
debug(`host.getScriptSnapshot("${specifier}")`);
|
||||
}
|
||||
let sourceFile = sourceFileCache.get(specifier);
|
||||
const isAsset = specifier.startsWith(ASSETS_URL_PREFIX);
|
||||
let sourceFile = isAsset
|
||||
? assetSourceFileCache.get(specifier)
|
||||
: sourceFileCache.get(specifier)?.deref();
|
||||
if (
|
||||
!specifier.startsWith(ASSETS_URL_PREFIX) &&
|
||||
!isAsset &&
|
||||
sourceFile?.version != this.getScriptVersion(specifier)
|
||||
) {
|
||||
sourceFileCache.delete(specifier);
|
||||
|
@ -994,14 +1010,12 @@ delete Object.prototype.__proto__;
|
|||
function getAssets() {
|
||||
/** @type {{ specifier: string; text: string; }[]} */
|
||||
const assets = [];
|
||||
for (const sourceFile of sourceFileCache.values()) {
|
||||
if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
|
||||
for (const sourceFile of assetSourceFileCache.values()) {
|
||||
assets.push({
|
||||
specifier: sourceFile.fileName,
|
||||
text: sourceFile.text,
|
||||
});
|
||||
}
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
|
@ -1177,8 +1191,9 @@ delete Object.prototype.__proto__;
|
|||
"lib.d.ts files have errors",
|
||||
);
|
||||
|
||||
assert(buildSpecifier.startsWith(ASSETS_URL_PREFIX));
|
||||
// remove this now that we don't need it anymore for warming up tsc
|
||||
sourceFileCache.delete(buildSpecifier);
|
||||
assetSourceFileCache.delete(buildSpecifier);
|
||||
|
||||
// exposes the functions that are called by `tsc::exec()` when type
|
||||
// checking TypeScript.
|
||||
|
|
Loading…
Reference in a new issue