Capture language server stderr during startup/init and log if failure

This commit is contained in:
Julia 2023-10-26 12:29:22 +02:00
parent 1936ba5e30
commit 170ebd8221
8 changed files with 68 additions and 38 deletions

2
Cargo.lock generated
View File

@ -1713,6 +1713,7 @@ dependencies = [
"log",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"rpc",
"serde",
"serde_derive",
@ -5562,6 +5563,7 @@ dependencies = [
"log",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"serde",
"serde_derive",
"serde_json",

View File

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

View File

@ -16,6 +16,7 @@ use language::{
};
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
use settings::SettingsStore;
use smol::{fs, io::BufReader, stream::StreamExt};
@ -387,8 +388,15 @@ impl Copilot {
path: node_path,
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
.on_notification::<StatusNotification, _>(

View File

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

View File

@ -136,6 +136,7 @@ struct Error {
impl LanguageServer {
pub fn new(
stderr_capture: Arc<Mutex<Option<String>>>,
server_id: LanguageServerId,
binary: LanguageServerBinary,
root_path: &Path,
@ -165,6 +166,7 @@ impl LanguageServer {
stdin,
stdout,
Some(stderr),
stderr_capture,
Some(server),
root_path,
code_action_kinds,
@ -197,6 +199,7 @@ impl LanguageServer {
stdin: Stdin,
stdout: Stdout,
stderr: Option<Stderr>,
stderr_capture: Arc<Mutex<Option<String>>>,
server: Option<Child>,
root_path: &Path,
code_action_kinds: Option<Vec<CodeActionKind>>,
@ -218,20 +221,23 @@ impl LanguageServer {
let io_handlers = Arc::new(Mutex::new(HashMap::default()));
let stdout_input_task = cx.spawn(|cx| {
{
Self::handle_input(
stdout,
on_unhandled_notification.clone(),
notification_handlers.clone(),
response_handlers.clone(),
io_handlers.clone(),
cx,
)
}
Self::handle_input(
stdout,
on_unhandled_notification.clone(),
notification_handlers.clone(),
response_handlers.clone(),
io_handlers.clone(),
cx,
)
.log_err()
});
let stderr_input_task = stderr
.map(|stderr| cx.spawn(|_| Self::handle_stderr(stderr, io_handlers.clone()).log_err()))
.map(|stderr| {
cx.spawn(|_| {
Self::handle_stderr(stderr, io_handlers.clone(), stderr_capture.clone())
.log_err()
})
})
.unwrap_or_else(|| Task::Ready(Some(None)));
let input_task = cx.spawn(|_| async move {
let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
@ -353,12 +359,14 @@ impl LanguageServer {
async fn handle_stderr<Stderr>(
stderr: Stderr,
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
stderr_capture: Arc<Mutex<Option<String>>>,
) -> anyhow::Result<()>
where
Stderr: AsyncRead + Unpin + Send + 'static,
{
let mut stderr = BufReader::new(stderr);
let mut buffer = Vec::new();
loop {
buffer.clear();
stderr.read_until(b'\n', &mut buffer).await?;
@ -367,6 +375,10 @@ impl LanguageServer {
for handler in io_handlers.lock().values_mut() {
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.
@ -938,6 +950,7 @@ impl LanguageServer {
stdin_writer,
stdout_reader,
None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None,
Path::new("/"),
None,
@ -950,6 +963,7 @@ impl LanguageServer {
stdout_writer,
stdin_reader,
None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None,
Path::new("/"),
None,

View File

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

View File

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

View File

@ -52,6 +52,7 @@ use lsp::{
};
use lsp_command::*;
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use postage::watch;
use prettier::{LocateStart, Prettier};
use project_settings::{LspSettings, ProjectSettings};
@ -2726,7 +2727,9 @@ impl Project {
return;
}
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
let pending_server = match self.languages.create_pending_language_server(
stderr_capture.clone(),
language.clone(),
adapter.clone(),
worktree_path,
@ -2763,10 +2766,14 @@ impl Project {
.await;
match result {
Ok(server) => server,
Ok(server) => {
stderr_capture.lock().take();
Some(server)
}
Err(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(&cx) {
if let Some(container_dir) = container_dir {
@ -2862,20 +2869,17 @@ impl Project {
server_id: LanguageServerId,
key: (WorktreeId, LanguageServerName),
cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> {
let setup = Self::setup_pending_language_server(
) -> Result<Arc<LanguageServer>> {
let language_server = Self::setup_pending_language_server(
this,
override_initialization_options,
pending_server,
adapter.clone(),
server_id,
cx,
);
)
.await?;
let language_server = match setup.await? {
Some(language_server) => language_server,
None => return Ok(None),
};
let this = match this.upgrade(cx) {
Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")),
@ -2892,7 +2896,7 @@ impl Project {
)
})?;
Ok(Some(language_server))
Ok(language_server)
}
async fn setup_pending_language_server(
@ -2902,12 +2906,9 @@ impl Project {
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> {
) -> Result<Arc<LanguageServer>> {
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx)).await;
let language_server = match pending_server.task.await? {
Some(server) => server,
None => return Ok(None),
};
let language_server = pending_server.task.await?;
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
@ -2978,6 +2979,7 @@ impl Project {
},
)
.detach();
language_server
.on_request::<lsp::request::RegisterCapability, _, _>({
move |params, mut cx| async move {
@ -3043,6 +3045,7 @@ impl Project {
}
})
.detach();
let mut initialization_options = adapter.adapter.initialization_options().await;
match (&mut initialization_options, override_options) {
(Some(initialization_options), Some(override_options)) => {
@ -3062,7 +3065,7 @@ impl Project {
)
.ok();
Ok(Some(language_server))
Ok(language_server)
}
fn insert_newly_running_language_server(