From 31e953fed7ea513bc2f458ec9bec6a8dd4d9ee78 Mon Sep 17 00:00:00 2001 From: wojciechkepka Date: Sat, 19 Jun 2021 04:56:50 +0200 Subject: [PATCH 1/3] Handle language server shutdown with timeout --- helix-lsp/src/client.rs | 15 +++++++++++++++ helix-lsp/src/lib.rs | 4 ++++ helix-term/src/application.rs | 13 ++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 245eb85446b8..101d2f9b1057 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -272,6 +272,21 @@ impl Client { self.notify::(()) } + /// Tries to shut down the language server but returns + /// early if server responds with an error. + pub async fn shutdown_and_exit(&self) -> Result<()> { + self.shutdown().await?; + self.exit().await + } + + /// Forcefully shuts down the language server ignoring any errors. + pub async fn force_shutdown(&self) -> Result<()> { + if let Err(e) = self.shutdown().await { + log::warn!("language server failed to terminate gracefully - {}", e); + } + self.exit().await + } + // ------------------------------------------------------------------------------------------- // Text document // ------------------------------------------------------------------------------------------- diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 774de075a11c..49d5527f722f 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -310,6 +310,10 @@ impl Registry { Err(Error::LspNotDefined) } } + + pub fn iter_clients(&self) -> impl Iterator> { + self.inner.values().map(|(_, client)| client) + } } #[derive(Debug)] diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index aa2ce884750f..1f02ac4f02b0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -23,7 +23,7 @@ use crossterm::{ use tui::layout::Rect; -use futures_util::stream::FuturesUnordered; +use futures_util::{future, stream::FuturesUnordered}; type BoxFuture = Pin + Send>>; pub type LspCallback = @@ -406,6 +406,17 @@ impl Application { self.event_loop().await; + tokio::time::timeout( + Duration::from_millis(500), + future::join_all( + self.editor + .language_servers + .iter_clients() + .map(|client| client.force_shutdown()), + ), + ) + .await; + // reset cursor shape write!(stdout, "\x1B[2 q"); From d59507f84464e74501ea9dac8927af84e514194a Mon Sep 17 00:00:00 2001 From: wojciechkepka Date: Sat, 19 Jun 2021 04:57:05 +0200 Subject: [PATCH 2/3] Fix unwraps in lsp::transport --- helix-lsp/src/transport.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs index 29ce2e84c66e..df55bbf648a7 100644 --- a/helix-lsp/src/transport.rs +++ b/helix-lsp/src/transport.rs @@ -1,4 +1,5 @@ use crate::Result; +use anyhow::Context; use jsonrpc_core as jsonrpc; use log::{error, info}; use serde::{Deserialize, Serialize}; @@ -90,7 +91,7 @@ impl Transport { match (parts.next(), parts.next(), parts.next()) { (Some("Content-Length"), Some(value), None) => { - content_length = Some(value.parse().unwrap()); + content_length = Some(value.parse().context("invalid content length")?); } (Some(_), Some(_), None) => {} _ => { @@ -103,12 +104,12 @@ impl Transport { } } - let content_length = content_length.unwrap(); + let content_length = content_length.context("missing content length")?; //TODO: reuse vector let mut content = vec![0; content_length]; reader.read_exact(&mut content).await?; - let msg = String::from_utf8(content).unwrap(); + let msg = String::from_utf8(content).context("invalid utf8 from server")?; info!("<- {}", msg); @@ -162,7 +163,9 @@ impl Transport { match msg { ServerMessage::Output(output) => self.process_request_response(output).await?, ServerMessage::Call(call) => { - self.client_tx.send((self.id, call)).unwrap(); + self.client_tx + .send((self.id, call)) + .context("failed to send a message to server")?; // let notification = Notification::parse(&method, params); } }; From e60a70634c6cb66a4260f155b3e1224860f83547 Mon Sep 17 00:00:00 2001 From: wojciechkepka Date: Sat, 19 Jun 2021 05:14:40 +0200 Subject: [PATCH 3/3] Add `close_language_servers` method on `Editor` --- Cargo.lock | 1 + helix-term/src/application.rs | 11 +---------- helix-view/Cargo.toml | 1 + helix-view/src/editor.rs | 20 ++++++++++++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12a03dac1dad..24c277e127d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,7 @@ version = "0.2.0" dependencies = [ "anyhow", "crossterm", + "futures-util", "helix-core", "helix-lsp", "helix-tui", diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 1f02ac4f02b0..2fae467fd84f 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -406,16 +406,7 @@ impl Application { self.event_loop().await; - tokio::time::timeout( - Duration::from_millis(500), - future::join_all( - self.editor - .language_servers - .iter_clients() - .map(|client| client.force_shutdown()), - ), - ) - .await; + self.editor.close_language_servers(None).await; // reset cursor shape write!(stdout, "\x1B[2 q"); diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 13539a5a222a..7f18e9a2aa0f 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -27,6 +27,7 @@ once_cell = "1.8" url = "2" tokio = { version = "1", features = ["full"] } +futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } slotmap = "1" diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index a1c93f755ca8..db8ae87abd1b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -2,7 +2,9 @@ use crate::{theme::Theme, tree::Tree, Document, DocumentId, RegisterSelection, V use tui::layout::Rect; use tui::terminal::CursorKind; +use futures_util::future; use std::path::PathBuf; +use std::time::Duration; use slotmap::SlotMap; @@ -270,4 +272,22 @@ impl Editor { (None, CursorKind::Hidden) } } + + /// Closes language servers with timeout. The default timeout is 500 ms, use + /// `timeout` parameter to override this. + pub async fn close_language_servers( + &self, + timeout: Option, + ) -> Result<(), tokio::time::error::Elapsed> { + tokio::time::timeout( + Duration::from_millis(timeout.unwrap_or(500)), + future::join_all( + self.language_servers + .iter_clients() + .map(|client| client.force_shutdown()), + ), + ) + .await + .map(|_| ()) + } }