mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 02:48:34 +00:00
Fix remaining language server hangs on shutdown
* Use fork of async-pipe library that handles closed pipes correctly. * Clear response handlers map when terminating output task, so as to wake any pending request futures. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
917543cc32
commit
95b2f4fb16
3 changed files with 26 additions and 59 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -336,7 +336,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-pipe"
|
name = "async-pipe"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "git+https://github.com/routerify/async-pipe-rs?rev=feeb77e83142a9ff837d0767652ae41bfc5d8e47#feeb77e83142a9ff837d0767652ae41bfc5d8e47"
|
source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2827,6 +2827,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-pipe",
|
"async-pipe",
|
||||||
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
@ -10,10 +10,11 @@ path = "src/lsp.rs"
|
||||||
test-support = ["async-pipe"]
|
test-support = ["async-pipe"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
collections = { path = "../collections" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-pipe = { git = "https://github.com/routerify/async-pipe-rs", rev = "feeb77e83142a9ff837d0767652ae41bfc5d8e47", optional = true }
|
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lsp-types = "0.91"
|
lsp-types = "0.91"
|
||||||
|
@ -26,7 +27,7 @@ smol = "1.2"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
async-pipe = { git = "https://github.com/routerify/async-pipe-rs", rev = "feeb77e83142a9ff837d0767652ae41bfc5d8e47" }
|
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
unindent = "0.1.7"
|
unindent = "0.1.7"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use futures::channel::oneshot;
|
use collections::HashMap;
|
||||||
use futures::{io::BufWriter, AsyncRead, AsyncWrite};
|
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite};
|
||||||
use gpui::{executor, Task};
|
use gpui::{executor, Task};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::{barrier, prelude::Stream, watch};
|
use postage::{barrier, prelude::Stream, watch};
|
||||||
|
@ -12,7 +12,6 @@ use smol::{
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
io::Write,
|
io::Write,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -129,14 +128,15 @@ impl LanguageServer {
|
||||||
let mut stdin = BufWriter::new(stdin);
|
let mut stdin = BufWriter::new(stdin);
|
||||||
let mut stdout = BufReader::new(stdout);
|
let mut stdout = BufReader::new(stdout);
|
||||||
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
||||||
let notification_handlers = Arc::new(RwLock::new(HashMap::<_, NotificationHandler>::new()));
|
let notification_handlers =
|
||||||
let response_handlers = Arc::new(Mutex::new(HashMap::<_, ResponseHandler>::new()));
|
Arc::new(RwLock::new(HashMap::<_, NotificationHandler>::default()));
|
||||||
|
let response_handlers = Arc::new(Mutex::new(HashMap::<_, ResponseHandler>::default()));
|
||||||
let input_task = executor.spawn(
|
let input_task = executor.spawn(
|
||||||
{
|
{
|
||||||
let notification_handlers = notification_handlers.clone();
|
let notification_handlers = notification_handlers.clone();
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
async move {
|
async move {
|
||||||
let _clear_response_channels = ClearResponseChannels(response_handlers.clone());
|
let _clear_response_handlers = ClearResponseHandlers(response_handlers.clone());
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
@ -190,8 +190,10 @@ impl LanguageServer {
|
||||||
.log_err(),
|
.log_err(),
|
||||||
);
|
);
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||||
let output_task = executor.spawn(
|
let output_task = executor.spawn({
|
||||||
|
let response_handlers = response_handlers.clone();
|
||||||
async move {
|
async move {
|
||||||
|
let _clear_response_handlers = ClearResponseHandlers(response_handlers);
|
||||||
let mut content_len_buffer = Vec::new();
|
let mut content_len_buffer = Vec::new();
|
||||||
while let Ok(message) = outbound_rx.recv().await {
|
while let Ok(message) = outbound_rx.recv().await {
|
||||||
content_len_buffer.clear();
|
content_len_buffer.clear();
|
||||||
|
@ -205,8 +207,8 @@ impl LanguageServer {
|
||||||
drop(output_done_tx);
|
drop(output_done_tx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err()
|
||||||
);
|
});
|
||||||
|
|
||||||
let (initialized_tx, initialized_rx) = barrier::channel();
|
let (initialized_tx, initialized_rx) = barrier::channel();
|
||||||
let (mut capabilities_tx, capabilities_rx) = watch::channel();
|
let (mut capabilities_tx, capabilities_rx) = watch::channel();
|
||||||
|
@ -408,9 +410,13 @@ impl LanguageServer {
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut response_handlers = response_handlers.lock();
|
|
||||||
|
let send = outbound_tx
|
||||||
|
.try_send(message)
|
||||||
|
.context("failed to write to language server's stdin");
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
response_handlers.insert(
|
response_handlers.lock().insert(
|
||||||
id,
|
id,
|
||||||
Box::new(move |result| {
|
Box::new(move |result| {
|
||||||
let response = match result {
|
let response = match result {
|
||||||
|
@ -423,9 +429,6 @@ impl LanguageServer {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let send = outbound_tx
|
|
||||||
.try_send(message)
|
|
||||||
.context("failed to write to language server's stdin");
|
|
||||||
async move {
|
async move {
|
||||||
send?;
|
send?;
|
||||||
rx.await?
|
rx.await?
|
||||||
|
@ -581,7 +584,7 @@ impl FakeLanguageServer {
|
||||||
});
|
});
|
||||||
|
|
||||||
let output_task = cx.background().spawn(async move {
|
let output_task = cx.background().spawn(async move {
|
||||||
let mut stdout = smol::io::BufWriter::new(PipeWriterCloseOnDrop(stdout));
|
let mut stdout = smol::io::BufWriter::new(stdout);
|
||||||
while let Some(message) = outgoing_rx.next().await {
|
while let Some(message) = outgoing_rx.next().await {
|
||||||
stdout
|
stdout
|
||||||
.write_all(CONTENT_LEN_HEADER.as_bytes())
|
.write_all(CONTENT_LEN_HEADER.as_bytes())
|
||||||
|
@ -694,7 +697,7 @@ impl FakeLanguageServer {
|
||||||
let message_len: usize = std::str::from_utf8(buffer)
|
let message_len: usize = std::str::from_utf8(buffer)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.strip_prefix(CONTENT_LEN_HEADER)
|
.strip_prefix(CONTENT_LEN_HEADER)
|
||||||
.unwrap()
|
.ok_or_else(|| anyhow!("invalid content length header"))?
|
||||||
.trim_end()
|
.trim_end()
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -704,47 +707,9 @@ impl FakeLanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PipeWriterCloseOnDrop(async_pipe::PipeWriter);
|
struct ClearResponseHandlers(Arc<Mutex<HashMap<usize, ResponseHandler>>>);
|
||||||
|
|
||||||
impl Drop for PipeWriterCloseOnDrop {
|
impl Drop for ClearResponseHandlers {
|
||||||
fn drop(&mut self) {
|
|
||||||
self.0.close().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncWrite for PipeWriterCloseOnDrop {
|
|
||||||
fn poll_write(
|
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> std::task::Poll<std::io::Result<usize>> {
|
|
||||||
let pipe = &mut self.0;
|
|
||||||
smol::pin!(pipe);
|
|
||||||
pipe.poll_write(cx, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(
|
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<std::io::Result<()>> {
|
|
||||||
let pipe = &mut self.0;
|
|
||||||
smol::pin!(pipe);
|
|
||||||
pipe.poll_flush(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_close(
|
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<std::io::Result<()>> {
|
|
||||||
let pipe = &mut self.0;
|
|
||||||
smol::pin!(pipe);
|
|
||||||
pipe.poll_close(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ClearResponseChannels(Arc<Mutex<HashMap<usize, ResponseHandler>>>);
|
|
||||||
|
|
||||||
impl Drop for ClearResponseChannels {
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.0.lock().clear();
|
self.0.lock().clear();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue