perf(lsp): release unused documents (#23398)

This commit is contained in:
Nayeem Rahman 2024-04-17 21:40:42 +01:00 committed by GitHub
parent 2dc3f6f57a
commit 24fa5c784a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 42 deletions

View File

@ -1015,6 +1015,11 @@ impl Documents {
Ok(()) Ok(())
} }
pub fn release(&self, specifier: &ModuleSpecifier) {
self.file_system_docs.remove_document(specifier);
self.file_system_docs.set_dirty(true);
}
/// Return `true` if the provided specifier can be resolved to a document, /// Return `true` if the provided specifier can be resolved to a document,
/// otherwise `false`. /// otherwise `false`.
pub fn contains_import( pub fn contains_import(

View File

@ -364,14 +364,11 @@ impl LanguageServer {
.client .client
.show_message(MessageType::WARNING, err); .show_message(MessageType::WARNING, err);
} }
{ let mut inner = self.0.write().await;
let mut inner = self.0.write().await; let lockfile = inner.config.tree.root_lockfile().cloned();
let lockfile = inner.config.tree.root_lockfile().cloned(); inner.documents.refresh_lockfile(lockfile);
inner.documents.refresh_lockfile(lockfile); inner.refresh_npm_specifiers().await;
inner.refresh_npm_specifiers().await; inner.post_cache(result.mark).await;
}
// now refresh the data in a read
self.0.read().await.post_cache(result.mark).await;
} }
Ok(Some(json!(true))) Ok(Some(json!(true)))
} }
@ -1421,7 +1418,16 @@ impl Inner {
self.recreate_npm_services_if_necessary().await; self.recreate_npm_services_if_necessary().await;
self.refresh_documents_config().await; self.refresh_documents_config().await;
self.diagnostics_server.invalidate_all(); self.diagnostics_server.invalidate_all();
self.ts_server.restart(self.snapshot()).await; self
.project_changed(
&changes
.iter()
.map(|(s, _)| (s, ChangeKind::Modified))
.collect::<Vec<_>>(),
false,
)
.await;
self.ts_server.cleanup_semantic_cache(self.snapshot()).await;
self.send_diagnostics_update(); self.send_diagnostics_update();
self.send_testing_update(); self.send_testing_update();
} }
@ -3544,13 +3550,14 @@ impl Inner {
})) }))
} }
async fn post_cache(&self, mark: PerformanceMark) { async fn post_cache(&mut self, mark: PerformanceMark) {
// Now that we have dependencies loaded, we need to re-analyze all the files. // Now that we have dependencies loaded, we need to re-analyze all the files.
// For that we're invalidating all the existing diagnostics and restarting // For that we're invalidating all the existing diagnostics and restarting
// the language server for TypeScript (as it might hold to some stale // the language server for TypeScript (as it might hold to some stale
// documents). // documents).
self.diagnostics_server.invalidate_all(); self.diagnostics_server.invalidate_all();
self.ts_server.restart(self.snapshot()).await; self.project_changed(&[], false).await;
self.ts_server.cleanup_semantic_cache(self.snapshot()).await;
self.send_diagnostics_update(); self.send_diagnostics_update();
self.send_testing_update(); self.send_testing_update();

View File

@ -356,6 +356,21 @@ impl TsServer {
Ok(diagnostics_map) Ok(diagnostics_map)
} }
pub async fn cleanup_semantic_cache(&self, snapshot: Arc<StateSnapshot>) {
let req = TscRequest {
method: "cleanupSemanticCache",
args: json!([]),
};
self
.request::<()>(snapshot, req)
.await
.map_err(|err| {
log::error!("Failed to request to tsserver {}", err);
LspError::invalid_request()
})
.ok();
}
pub async fn find_references( pub async fn find_references(
&self, &self,
snapshot: Arc<StateSnapshot>, snapshot: Arc<StateSnapshot>,
@ -1010,14 +1025,6 @@ impl TsServer {
}) })
} }
pub async fn restart(&self, snapshot: Arc<StateSnapshot>) {
let req = TscRequest {
method: "$restart",
args: json!([]),
};
self.request::<bool>(snapshot, req).await.unwrap();
}
async fn request<R>( async fn request<R>(
&self, &self,
snapshot: Arc<StateSnapshot>, snapshot: Arc<StateSnapshot>,
@ -4032,6 +4039,21 @@ fn op_load<'s>(
Ok(serialized) Ok(serialized)
} }
#[op2(fast)]
fn op_release(
state: &mut OpState,
#[string] specifier: &str,
) -> Result<(), AnyError> {
let state = state.borrow_mut::<State>();
let mark = state
.performance
.mark_with_args("tsc.op.op_release", specifier);
let specifier = state.specifier_map.normalize(specifier)?;
state.state_snapshot.documents.release(&specifier);
state.performance.measure(mark);
Ok(())
}
#[op2] #[op2]
#[serde] #[serde]
fn op_resolve( fn op_resolve(
@ -4244,6 +4266,7 @@ deno_core::extension!(deno_tsc,
op_is_cancelled, op_is_cancelled,
op_is_node_file, op_is_node_file,
op_load, op_load,
op_release,
op_resolve, op_resolve,
op_respond, op_respond,
op_script_names, op_script_names,

View File

@ -155,6 +155,12 @@ delete Object.prototype.__proto__;
/** @type {Map<string, ts.SourceFile>} */ /** @type {Map<string, ts.SourceFile>} */
const sourceFileCache = new Map(); const sourceFileCache = new Map();
/** @type {Map<string, string>} */
const sourceTextCache = new Map();
/** @type {Map<string, number>} */
const sourceRefCounts = new Map();
/** @type {string[]=} */ /** @type {string[]=} */
let scriptFileNamesCache; let scriptFileNamesCache;
@ -172,6 +178,8 @@ delete Object.prototype.__proto__;
/** @type {number | null} */ /** @type {number | null} */
let projectVersionCache = null; let projectVersionCache = null;
let lastRequestMethod = null;
const ChangeKind = { const ChangeKind = {
Opened: 0, Opened: 0,
Modified: 1, Modified: 1,
@ -250,6 +258,8 @@ delete Object.prototype.__proto__;
); );
documentRegistrySourceFileCache.set(mapKey, sourceFile); documentRegistrySourceFileCache.set(mapKey, sourceFile);
} }
const sourceRefCount = sourceRefCounts.get(fileName) ?? 0;
sourceRefCounts.set(fileName, sourceRefCount + 1);
return sourceFile; return sourceFile;
}, },
@ -333,8 +343,20 @@ delete Object.prototype.__proto__;
}, },
releaseDocumentWithKey(path, key, _scriptKind, _impliedNodeFormat) { releaseDocumentWithKey(path, key, _scriptKind, _impliedNodeFormat) {
const mapKey = path + key; const sourceRefCount = sourceRefCounts.get(path) ?? 1;
documentRegistrySourceFileCache.delete(mapKey); if (sourceRefCount <= 1) {
sourceRefCounts.delete(path);
// We call `cleanupSemanticCache` for other purposes, don't bust the
// source cache in this case.
if (lastRequestMethod != "cleanupSemanticCache") {
const mapKey = path + key;
documentRegistrySourceFileCache.delete(mapKey);
sourceTextCache.delete(path);
ops.op_release(path);
}
} else {
sourceRefCounts.set(path, sourceRefCount - 1);
}
}, },
reportStats() { reportStats() {
@ -807,19 +829,26 @@ delete Object.prototype.__proto__;
if (logDebug) { if (logDebug) {
debug(`host.getScriptSnapshot("${specifier}")`); debug(`host.getScriptSnapshot("${specifier}")`);
} }
let sourceFile = sourceFileCache.get(specifier); const sourceFile = sourceFileCache.get(specifier);
if (!sourceFile) {
sourceFile = this.getSourceFile(
specifier,
specifier.endsWith(".json")
? ts.ScriptTarget.JSON
: ts.ScriptTarget.ESNext,
);
}
if (sourceFile) { if (sourceFile) {
// This case only occurs for assets.
return ts.ScriptSnapshot.fromString(sourceFile.text); return ts.ScriptSnapshot.fromString(sourceFile.text);
} }
return undefined; let sourceText = sourceTextCache.get(specifier);
if (sourceText == undefined) {
/** @type {{ data: string, version: string, isCjs: boolean }} */
const fileInfo = ops.op_load(specifier);
if (!fileInfo) {
return undefined;
}
if (fileInfo.isCjs) {
isCjsCache.add(specifier);
}
sourceTextCache.set(specifier, fileInfo.data);
scriptVersionCache.set(specifier, fileInfo.version);
sourceText = fileInfo.data;
}
return ts.ScriptSnapshot.fromString(sourceText);
}, },
}; };
@ -1047,6 +1076,7 @@ delete Object.prototype.__proto__;
if (logDebug) { if (logDebug) {
debug(`serverRequest()`, id, method, args); debug(`serverRequest()`, id, method, args);
} }
lastRequestMethod = method;
switch (method) { switch (method) {
case "$projectChanged": { case "$projectChanged": {
/** @type {[string, number][]} */ /** @type {[string, number][]} */
@ -1058,6 +1088,7 @@ delete Object.prototype.__proto__;
if (configChanged) { if (configChanged) {
tsConfigCache = null; tsConfigCache = null;
isNodeSourceFileCache.clear();
} }
projectVersionCache = newProjectVersion; projectVersionCache = newProjectVersion;
@ -1068,7 +1099,7 @@ delete Object.prototype.__proto__;
opened = true; opened = true;
} }
scriptVersionCache.delete(script); scriptVersionCache.delete(script);
sourceFileCache.delete(script); sourceTextCache.delete(script);
} }
if (configChanged || opened) { if (configChanged || opened) {
@ -1077,10 +1108,6 @@ delete Object.prototype.__proto__;
return respond(id); return respond(id);
} }
case "$restart": {
serverRestart();
return respond(id, true);
}
case "$getSupportedCodeFixes": { case "$getSupportedCodeFixes": {
return respond( return respond(
id, id,
@ -1152,12 +1179,6 @@ delete Object.prototype.__proto__;
debug("serverInit()"); debug("serverInit()");
} }
function serverRestart() {
languageService = ts.createLanguageService(host, documentRegistry);
isNodeSourceFileCache.clear();
debug("serverRestart()");
}
// A build time only op that provides some setup information that is used to // A build time only op that provides some setup information that is used to
// ensure the snapshot is setup properly. // ensure the snapshot is setup properly.
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */ /** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */