From 0ef3f6ba887d7aed2d94c8b622563d13bfecda2c Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Thu, 6 Mar 2025 18:05:25 +0000 Subject: [PATCH] perf(lsp): lazily start the ts server (#28392) --- cli/lsp/diagnostics.rs | 1 - cli/lsp/language_server.rs | 50 +++++++++++-------- cli/lsp/tsc.rs | 91 +++++++++++++++++++++------------- tests/integration/lsp_tests.rs | 4 +- 4 files changed, 86 insertions(+), 60 deletions(-) diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 29629db47b..a30b269428 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -2022,7 +2022,6 @@ let c: number = "a"; .await; let snapshot = Arc::new(snapshot); let ts_server = TsServer::new(Default::default()); - ts_server.start(None).unwrap(); // test enabled { diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 781321820a..12bdd5d311 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -213,9 +213,7 @@ pub struct Inner { registered_semantic_tokens_capabilities: bool, pub resolver: Arc, task_queue: LanguageServerTaskQueue, - /// A memoized version of fixable diagnostic codes retrieved from TypeScript. - ts_fixable_diagnostics: Vec, - /// An abstraction that handles interactions with TypeScript. + ts_fixable_diagnostics: tokio::sync::OnceCell>, pub ts_server: Arc, /// A map of specifiers and URLs used to translate over the LSP. pub url_map: urls::LspUrlMap, @@ -621,6 +619,19 @@ impl Inner { }) } + pub async fn ts_fixable_diagnostics(&self) -> &Vec { + self + .ts_fixable_diagnostics + .get_or_init(|| async { + self + .ts_server + .get_supported_code_fixes(self.snapshot()) + .await + .unwrap() + }) + .await + } + pub fn update_tracing(&mut self) { let tracing = self @@ -777,7 +788,7 @@ impl Inner { // lspower::LanguageServer methods. This file's LanguageServer delegates to us. impl Inner { - async fn initialize( + fn initialize( &mut self, params: InitializeParams, ) -> LspResult { @@ -862,26 +873,13 @@ impl Inner { } self.diagnostics_server.start(); - if let Err(e) = self + self .ts_server - .start(self.config.internal_inspect().to_address()) - { - lsp_warn!("{}", e); - self.client.show_message(MessageType::ERROR, e); - return Err(tower_lsp::jsonrpc::Error::internal_error()); - }; + .set_inspector_server_addr(self.config.internal_inspect().to_address()); self.update_tracing(); self.update_debug_flag(); - if capabilities.code_action_provider.is_some() { - let fixable_diagnostics = self - .ts_server - .get_supported_code_fixes(self.snapshot()) - .await?; - self.ts_fixable_diagnostics = fixable_diagnostics; - } - if capabilities.semantic_tokens_provider.is_some() { self.registered_semantic_tokens_capabilities = true; } @@ -1746,6 +1744,7 @@ impl Inner { let line_index = asset_or_doc.line_index(); // QuickFix + let ts_fixable_diagnosics = self.ts_fixable_diagnostics().await; let fixable_diagnostics: Vec<&Diagnostic> = params .context .diagnostics @@ -1754,10 +1753,10 @@ impl Inner { Some(source) => match source.as_str() { "deno-ts" => match &d.code { Some(NumberOrString::String(code)) => { - self.ts_fixable_diagnostics.contains(code) + ts_fixable_diagnosics.contains(code) } Some(NumberOrString::Number(code)) => { - self.ts_fixable_diagnostics.contains(&code.to_string()) + ts_fixable_diagnosics.contains(&code.to_string()) } _ => false, }, @@ -3399,6 +3398,9 @@ impl Inner { params: RenameFilesParams, token: &CancellationToken, ) -> LspResult> { + if !self.ts_server.is_started() { + return Ok(None); + } let mut changes = vec![]; for rename in params.files { let old_specifier = self.url_map.uri_to_specifier( @@ -3461,6 +3463,10 @@ impl Inner { params: WorkspaceSymbolParams, token: &CancellationToken, ) -> LspResult>> { + if !self.ts_server.is_started() { + return Ok(None); + } + let mark = self.performance.mark_with_args("lsp.symbol", ¶ms); let navigate_to_items = self @@ -3588,7 +3594,7 @@ impl tower_lsp::LanguageServer for LanguageServer { &self, params: InitializeParams, ) -> LspResult { - self.inner.write().await.initialize(params).await + self.inner.write().await.initialize(params) } async fn initialized(&self, _: InitializedParams) { diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index a8bbf08649..c2c1664b7d 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -19,7 +19,6 @@ use std::thread; use dashmap::DashMap; use deno_ast::MediaType; use deno_core::anyhow::anyhow; -use deno_core::anyhow::Context as _; use deno_core::convert::Smi; use deno_core::convert::ToV8; use deno_core::error::AnyError; @@ -245,9 +244,11 @@ pub struct TsServer { sender: mpsc::UnboundedSender, receiver: Mutex>>, pub specifier_map: Arc, + inspector_server_addr: Mutex>, inspector_server: Mutex>>, pending_change: Mutex>, enable_tracing: Arc, + start_once: std::sync::Once, } impl std::fmt::Debug for TsServer { @@ -257,7 +258,9 @@ impl std::fmt::Debug for TsServer { .field("sender", &self.sender) .field("receiver", &self.receiver) .field("specifier_map", &self.specifier_map) + .field("inspector_server_addr", &self.inspector_server_addr.lock()) .field("inspector_server", &self.inspector_server.lock().is_some()) + .field("start_once", &self.start_once) .finish() } } @@ -404,9 +407,11 @@ impl TsServer { sender: tx, receiver: Mutex::new(Some(request_rx)), specifier_map: Arc::new(TscSpecifierMap::new()), + inspector_server_addr: Mutex::new(None), inspector_server: Mutex::new(None), pending_change: Mutex::new(None), enable_tracing: Default::default(), + start_once: std::sync::Once::new(), } } @@ -416,40 +421,53 @@ impl TsServer { .store(enabled, std::sync::atomic::Ordering::Relaxed); } - pub fn start( - &self, - inspector_server_addr: Option, - ) -> Result<(), AnyError> { - let maybe_inspector_server = match inspector_server_addr { - Some(addr) => { - let addr: SocketAddr = addr.parse().with_context(|| { - format!("Invalid inspector server address \"{}\"", &addr) - })?; - let server = InspectorServer::new(addr, "deno-lsp-tsc")?; - Some(Arc::new(server)) - } - None => None, - }; - self - .inspector_server - .lock() - .clone_from(&maybe_inspector_server); - // TODO(bartlomieju): why is the join_handle ignored here? Should we store it - // on the `TsServer` struct. - let receiver = self.receiver.lock().take().unwrap(); - let performance = self.performance.clone(); - let specifier_map = self.specifier_map.clone(); - let enable_tracing = self.enable_tracing.clone(); - let _join_handle = thread::spawn(move || { - run_tsc_thread( - receiver, - performance, - specifier_map, - maybe_inspector_server, - enable_tracing, - ) + /// This should be called before `self.ensure_started()`. + pub fn set_inspector_server_addr(&self, addr: Option) { + *self.inspector_server_addr.lock() = addr; + } + + pub fn ensure_started(&self) { + self.start_once.call_once(|| { + let maybe_inspector_server = self + .inspector_server_addr + .lock() + .as_ref() + .and_then(|addr| { + addr + .parse::() + .inspect_err(|err| { + lsp_warn!("Invalid inspector server address: {:#}", err); + }) + .ok() + }) + .map(|addr| { + Arc::new(InspectorServer::new(addr, "deno-lsp-tsc").unwrap()) + }); + self + .inspector_server + .lock() + .clone_from(&maybe_inspector_server); + // TODO(bartlomieju): why is the join_handle ignored here? Should we store it + // on the `TsServer` struct. + let receiver = self.receiver.lock().take().unwrap(); + let performance = self.performance.clone(); + let specifier_map = self.specifier_map.clone(); + let enable_tracing = self.enable_tracing.clone(); + let _join_handle = thread::spawn(move || { + run_tsc_thread( + receiver, + performance, + specifier_map, + maybe_inspector_server, + enable_tracing, + ) + }); + lsp_log!("TS server started."); }); - Ok(()) + } + + pub fn is_started(&self) -> bool { + self.start_once.is_completed() } pub fn project_changed<'a>( @@ -549,6 +567,9 @@ impl TsServer { #[cfg_attr(feature = "lsp-tracing", tracing::instrument(skip_all))] pub async fn cleanup_semantic_cache(&self, snapshot: Arc) { + if !self.is_started() { + return; + } for scope in snapshot .config .tree @@ -1365,6 +1386,7 @@ impl TsServer { R: de::DeserializeOwned, { use super::trace::SpanExt; + self.ensure_started(); let context = super::trace::Span::current().context(); let mark = self .performance @@ -5772,7 +5794,6 @@ mod tests { }); let performance = Arc::new(Performance::default()); let ts_server = TsServer::new(performance); - ts_server.start(None).unwrap(); ts_server.project_changed( snapshot.clone(), [], diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 717986e48c..ca90a75796 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -11488,13 +11488,11 @@ fn lsp_performance() { "lsp.update_diagnostics_ts", "lsp.update_global_cache", "tsc.host.$getDiagnostics", - "tsc.host.$getSupportedCodeFixes", "tsc.host.getQuickInfoAtPosition", "tsc.op.op_is_node_file", "tsc.op.op_load", "tsc.op.op_script_names", "tsc.request.$getDiagnostics", - "tsc.request.$getSupportedCodeFixes", "tsc.request.getQuickInfoAtPosition", ] ); @@ -15135,6 +15133,7 @@ fn lsp_deno_json_scopes_file_rename_import_edits() { ); let mut client = context.new_lsp_command().build(); client.initialize_default(); + client.did_open_file(&file1); let res = client.write_request( "workspace/willRenameFiles", json!({ @@ -15395,6 +15394,7 @@ fn lsp_deno_json_scopes_search_symbol() { ); let mut client = context.new_lsp_command().build(); client.initialize_default(); + client.did_open_file(&file1); let res = client.write_request("workspace/symbol", json!({ "query": "someSymbol" })); assert_eq!(