fix(lsp): close server on exit notification (#28232)

This commit is contained in:
Nayeem Rahman 2025-02-21 20:02:56 +00:00 committed by GitHub
parent a9f404e479
commit 876bac445a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 19 additions and 35 deletions

8
Cargo.lock generated
View file

@ -1570,9 +1570,9 @@ dependencies = [
[[package]]
name = "deno-tower-lsp-macros"
version = "0.10.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e017686632f7908cde3e02cd1598e25488f57448600a09211834cfd547b9ec39"
checksum = "61083feefc9c29a3f45670be6b60001ec209286c861b6830cd30ba4af9a6e9d8"
dependencies = [
"proc-macro2",
"quote",
@ -2691,9 +2691,9 @@ dependencies = [
[[package]]
name = "deno_tower_lsp"
version = "0.3.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58d75640e2edffc77929064f2f13c308e549b4b5c42ea6159b0607add856675"
checksum = "c29dce82d67177bbc36468f64cddd8c25242fc31b99369ef0126fa7fad64c5ec"
dependencies = [
"async-trait",
"auto_impl",

View file

@ -218,7 +218,7 @@ tokio-socks = "0.5.1"
tokio-util = "0.7.4"
tower = { version = "0.5.2", default-features = false, features = ["retry", "util"] }
tower-http = { version = "0.6.1", features = ["decompression-br", "decompression-gzip"] }
tower-lsp = { package = "deno_tower_lsp", version = "=0.3.0", features = ["proposed"] }
tower-lsp = { package = "deno_tower_lsp", version = "=0.4.2", features = ["proposed"] }
tower-service = "0.3.2"
twox-hash = "=1.6.3"
url = { version = "2.5", features = ["serde", "expose_internals"] }

View file

@ -137,7 +137,6 @@ pub struct LanguageServer {
/// https://github.com/Microsoft/language-server-protocol/issues/567#issuecomment-2085131917
init_flag: AsyncFlag,
performance: Arc<Performance>,
shutdown_flag: AsyncFlag,
}
/// Snapshot of the state used by TSC.
@ -227,7 +226,7 @@ pub struct Inner {
}
impl LanguageServer {
pub fn new(client: Client, shutdown_flag: AsyncFlag) -> Self {
pub fn new(client: Client) -> Self {
let performance = Arc::new(Performance::default());
Self {
client: client.clone(),
@ -237,7 +236,6 @@ impl LanguageServer {
))),
init_flag: Default::default(),
performance,
shutdown_flag,
}
}
@ -3566,7 +3564,6 @@ impl tower_lsp::LanguageServer for LanguageServer {
}
async fn shutdown(&self) -> LspResult<()> {
self.shutdown_flag.raise();
Ok(())
}

View file

@ -1,7 +1,6 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_core::error::AnyError;
use deno_core::unsync::spawn;
pub use repl::ReplCompletionItem;
pub use repl::ReplLanguageServer;
use tower_lsp::LspService;
@ -9,7 +8,6 @@ use tower_lsp::Server;
use self::diagnostics::should_send_diagnostic_batch_index_notifications;
use crate::lsp::language_server::LanguageServer;
use crate::util::sync::AsyncFlag;
mod analysis;
mod cache;
@ -44,12 +42,8 @@ pub async fn start() -> Result<(), AnyError> {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let shutdown_flag = AsyncFlag::default();
let builder = LspService::build(|client| {
language_server::LanguageServer::new(
client::Client::from_tower(client),
shutdown_flag.clone(),
)
language_server::LanguageServer::new(client::Client::from_tower(client))
})
.custom_method(
lsp_custom::PERFORMANCE_REQUEST,
@ -76,19 +70,9 @@ pub async fn start() -> Result<(), AnyError> {
};
let (service, socket, pending) = builder.finish();
// TODO(nayeemrmn): This shutdown flag is a workaround for
// https://github.com/denoland/deno/issues/20700. Remove when
// https://github.com/ebkalderon/tower-lsp/issues/399 is fixed.
// Force end the server 8 seconds after receiving a shutdown request.
tokio::select! {
biased;
_ = Server::new(stdin, stdout, socket, pending).concurrency_level(32).serve(service) => {}
_ = spawn(async move {
shutdown_flag.wait_raised().await;
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
}) => {}
}
Server::new(stdin, stdout, socket, pending)
.concurrency_level(32)
.serve(service)
.await;
Ok(())
}

View file

@ -65,10 +65,8 @@ impl ReplLanguageServer {
super::logging::set_lsp_log_level(log::Level::Debug);
super::logging::set_lsp_warn_level(log::Level::Debug);
let language_server = super::language_server::LanguageServer::new(
Client::new_for_repl(),
Default::default(),
);
let language_server =
super::language_server::LanguageServer::new(Client::new_for_repl());
let cwd_uri = get_cwd_uri()?;

View file

@ -27,6 +27,7 @@ fn lsp_startup_shutdown() {
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.shutdown();
assert!(client.wait_exit().unwrap().success());
}
#[test]

View file

@ -731,7 +731,7 @@ impl Drop for LspClient {
self.child.kill().unwrap();
let _ = self.child.wait();
}
Ok(Some(status)) => panic!("deno lsp exited unexpectedly {status}"),
Ok(Some(_)) => {}
Err(e) => panic!("pebble error: {e}"),
}
}
@ -1002,6 +1002,10 @@ impl LspClient {
self.write_notification("exit", json!(null));
}
pub fn wait_exit(&mut self) -> std::io::Result<std::process::ExitStatus> {
self.child.wait()
}
// it's flaky to assert for a notification because a notification
// might arrive a little later, so only provide a method for asserting
// that there is no notification