zed2: Capture language server stderr during startup/init and log if failure (#3176)

zed2 electric boogaloo

Release Notes:

- N/A
This commit is contained in:
Julia 2023-10-26 13:59:04 +02:00 committed by GitHub
commit 0eafb8886d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 54 additions and 27 deletions

2
Cargo.lock generated
View file

@ -1836,6 +1836,7 @@ dependencies = [
"log", "log",
"lsp2", "lsp2",
"node_runtime", "node_runtime",
"parking_lot 0.11.2",
"rpc", "rpc",
"serde", "serde",
"serde_derive", "serde_derive",
@ -5906,6 +5907,7 @@ dependencies = [
"log", "log",
"lsp2", "lsp2",
"node_runtime", "node_runtime",
"parking_lot 0.11.2",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",

View file

@ -36,6 +36,7 @@ serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
smol.workspace = true smol.workspace = true
futures.workspace = true futures.workspace = true
parking_lot.workspace = true
[dev-dependencies] [dev-dependencies]
clock = { path = "../clock" } clock = { path = "../clock" }

View file

@ -17,6 +17,7 @@ use language2::{
}; };
use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId}; use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification; use request::StatusNotification;
use settings2::SettingsStore; use settings2::SettingsStore;
use smol::{fs, io::BufReader, stream::StreamExt}; use smol::{fs, io::BufReader, stream::StreamExt};
@ -394,8 +395,15 @@ impl Copilot {
path: node_path, path: node_path,
arguments, arguments,
}; };
let server =
LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?; let server = LanguageServer::new(
Arc::new(Mutex::new(None)),
new_server_id,
binary,
Path::new("/"),
None,
cx.clone(),
)?;
server server
.on_notification::<StatusNotification, _>( .on_notification::<StatusNotification, _>(

View file

@ -667,7 +667,7 @@ struct LanguageRegistryState {
pub struct PendingLanguageServer { pub struct PendingLanguageServer {
pub server_id: LanguageServerId, pub server_id: LanguageServerId,
pub task: Task<Result<Option<lsp2::LanguageServer>>>, pub task: Task<Result<lsp2::LanguageServer>>,
pub container_dir: Option<Arc<Path>>, pub container_dir: Option<Arc<Path>>,
} }
@ -906,6 +906,7 @@ impl LanguageRegistry {
pub fn create_pending_language_server( pub fn create_pending_language_server(
self: &Arc<Self>, self: &Arc<Self>,
stderr_capture: Arc<Mutex<Option<String>>>,
language: Arc<Language>, language: Arc<Language>,
adapter: Arc<CachedLspAdapter>, adapter: Arc<CachedLspAdapter>,
root_path: Arc<Path>, root_path: Arc<Path>,
@ -945,7 +946,7 @@ impl LanguageRegistry {
}) })
.detach(); .detach();
Ok(Some(server)) Ok(server)
}); });
return Some(PendingLanguageServer { return Some(PendingLanguageServer {
@ -996,24 +997,23 @@ impl LanguageRegistry {
}) })
.clone(); .clone();
let binary = match entry.await.log_err() { let binary = match entry.await {
Some(binary) => binary, Ok(binary) => binary,
None => return Ok(None), Err(err) => anyhow::bail!("{err}"),
}; };
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
if task.await.log_err().is_none() { task.await?;
return Ok(None);
}
} }
Ok(Some(lsp2::LanguageServer::new( lsp2::LanguageServer::new(
stderr_capture,
server_id, server_id,
binary, binary,
&root_path, &root_path,
adapter.code_action_kinds(), adapter.code_action_kinds(),
cx, cx,
)?)) )
}) })
}; };

View file

@ -136,6 +136,7 @@ struct Error {
impl LanguageServer { impl LanguageServer {
pub fn new( pub fn new(
stderr_capture: Arc<Mutex<Option<String>>>,
server_id: LanguageServerId, server_id: LanguageServerId,
binary: LanguageServerBinary, binary: LanguageServerBinary,
root_path: &Path, root_path: &Path,
@ -165,6 +166,7 @@ impl LanguageServer {
stdin, stdin,
stdout, stdout,
Some(stderr), Some(stderr),
stderr_capture,
Some(server), Some(server),
root_path, root_path,
code_action_kinds, code_action_kinds,
@ -197,6 +199,7 @@ impl LanguageServer {
stdin: Stdin, stdin: Stdin,
stdout: Stdout, stdout: Stdout,
stderr: Option<Stderr>, stderr: Option<Stderr>,
stderr_capture: Arc<Mutex<Option<String>>>,
server: Option<Child>, server: Option<Child>,
root_path: &Path, root_path: &Path,
code_action_kinds: Option<Vec<CodeActionKind>>, code_action_kinds: Option<Vec<CodeActionKind>>,
@ -237,7 +240,8 @@ impl LanguageServer {
let stderr_input_task = stderr let stderr_input_task = stderr
.map(|stderr| { .map(|stderr| {
let io_handlers = io_handlers.clone(); let io_handlers = io_handlers.clone();
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers).log_err()) let stderr_captures = stderr_captures.clone();
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
}) })
.unwrap_or_else(|| Task::Ready(Some(None))); .unwrap_or_else(|| Task::Ready(Some(None)));
let input_task = cx.spawn(|_| async move { let input_task = cx.spawn(|_| async move {
@ -360,12 +364,14 @@ impl LanguageServer {
async fn handle_stderr<Stderr>( async fn handle_stderr<Stderr>(
stderr: Stderr, stderr: Stderr,
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>, io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
stderr_capture: Arc<Mutex<Option<String>>>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
Stderr: AsyncRead + Unpin + Send + 'static, Stderr: AsyncRead + Unpin + Send + 'static,
{ {
let mut stderr = BufReader::new(stderr); let mut stderr = BufReader::new(stderr);
let mut buffer = Vec::new(); let mut buffer = Vec::new();
loop { loop {
buffer.clear(); buffer.clear();
stderr.read_until(b'\n', &mut buffer).await?; stderr.read_until(b'\n', &mut buffer).await?;
@ -374,6 +380,10 @@ impl LanguageServer {
for handler in io_handlers.lock().values_mut() { for handler in io_handlers.lock().values_mut() {
handler(IoKind::StdErr, message); handler(IoKind::StdErr, message);
} }
if let Some(stderr) = stderr_capture.lock().as_mut() {
stderr.push_str(message);
}
} }
// Don't starve the main thread when receiving lots of messages at once. // Don't starve the main thread when receiving lots of messages at once.
@ -933,6 +943,7 @@ impl LanguageServer {
stdin_writer, stdin_writer,
stdout_reader, stdout_reader,
None::<async_pipe::PipeReader>, None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None, None,
Path::new("/"), Path::new("/"),
None, None,
@ -945,6 +956,7 @@ impl LanguageServer {
stdout_writer, stdout_writer,
stdin_reader, stdin_reader,
None::<async_pipe::PipeReader>, None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None, None,
Path::new("/"), Path::new("/"),
None, None,

View file

@ -27,6 +27,7 @@ serde_derive.workspace = true
serde_json.workspace = true serde_json.workspace = true
anyhow.workspace = true anyhow.workspace = true
futures.workspace = true futures.workspace = true
parking_lot.workspace = true
[dev-dependencies] [dev-dependencies]
language2 = { path = "../language2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] }

View file

@ -210,6 +210,7 @@ impl Prettier {
.spawn(async move { node.binary_path().await }) .spawn(async move { node.binary_path().await })
.await?; .await?;
let server = LanguageServer::new( let server = LanguageServer::new(
Arc::new(parking_lot::Mutex::new(None)),
server_id, server_id,
LanguageServerBinary { LanguageServerBinary {
path: node_path, path: node_path,

View file

@ -52,6 +52,7 @@ use lsp2::{
}; };
use lsp_command::*; use lsp_command::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use postage::watch; use postage::watch;
use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS}; use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS};
use project_settings::{LspSettings, ProjectSettings}; use project_settings::{LspSettings, ProjectSettings};
@ -2778,7 +2779,9 @@ impl Project {
return; return;
} }
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
let pending_server = match self.languages.create_pending_language_server( let pending_server = match self.languages.create_pending_language_server(
stderr_capture.clone(),
language.clone(), language.clone(),
adapter.clone(), adapter.clone(),
worktree_path, worktree_path,
@ -2824,10 +2827,14 @@ impl Project {
.await; .await;
match result { match result {
Ok(server) => server, Ok(server) => {
stderr_capture.lock().take();
server
}
Err(err) => { Err(err) => {
log::error!("failed to start language server {:?}: {}", server_name, err); log::error!("failed to start language server {:?}: {}", server_name, err);
log::error!("server stderr: {:?}", stderr_capture.lock().take());
if let Some(this) = this.upgrade() { if let Some(this) = this.upgrade() {
if let Some(container_dir) = container_dir { if let Some(container_dir) = container_dir {
@ -2931,19 +2938,16 @@ impl Project {
key: (WorktreeId, LanguageServerName), key: (WorktreeId, LanguageServerName),
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> { ) -> Result<Option<Arc<LanguageServer>>> {
let setup = Self::setup_pending_language_server( let language_server = Self::setup_pending_language_server(
this.clone(), this.clone(),
initialization_options, initialization_options,
pending_server, pending_server,
adapter.clone(), adapter.clone(),
server_id, server_id,
cx, cx,
); )
.await?;
let language_server = match setup.await? {
Some(language_server) => language_server,
None => return Ok(None),
};
let this = match this.upgrade() { let this = match this.upgrade() {
Some(this) => this, Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")), None => return Err(anyhow!("failed to upgrade project handle")),
@ -2960,7 +2964,7 @@ impl Project {
) )
})??; })??;
Ok(Some(language_server)) Ok(language_server)
} }
async fn setup_pending_language_server( async fn setup_pending_language_server(
@ -2970,12 +2974,9 @@ impl Project {
adapter: Arc<CachedLspAdapter>, adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId, server_id: LanguageServerId,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> { ) -> Result<Arc<LanguageServer>> {
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await; let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
let language_server = match pending_server.task.await? { let language_server = pending_server.task.await?;
Some(server) => server,
None => return Ok(None),
};
language_server language_server
.on_notification::<lsp2::notification::PublishDiagnostics, _>({ .on_notification::<lsp2::notification::PublishDiagnostics, _>({
@ -3050,6 +3051,7 @@ impl Project {
} }
}) })
.detach(); .detach();
language_server language_server
.on_request::<lsp2::request::RegisterCapability, _, _>({ .on_request::<lsp2::request::RegisterCapability, _, _>({
let this = this.clone(); let this = this.clone();
@ -3138,7 +3140,7 @@ impl Project {
) )
.ok(); .ok();
Ok(Some(language_server)) Ok(language_server)
} }
fn insert_newly_running_language_server( fn insert_newly_running_language_server(