lsp: if language server closes stdout/stderr, break loop (#7229)

Thorsten Ball , Julia , and Mikayla created

Previously we would run these loops indefinitely when a language server
closed its stdout/stderr and the `read_until` returned `0` bytes read.

Easy to reproduce: start Zed with LSP attached, `kill -9` the LSP, see
logs accumulate.

Release Notes:

- Fix high CPU usage when a language server crashes (or closes its
stdout/stderr on purpose).

Co-authored-by: Julia <julia@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

crates/lsp/src/lsp.rs | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)

Detailed changes

crates/lsp/src/lsp.rs 🔗

@@ -322,8 +322,15 @@ impl LanguageServer {
         let mut buffer = Vec::new();
         loop {
             buffer.clear();
-            stdout.read_until(b'\n', &mut buffer).await?;
-            stdout.read_until(b'\n', &mut buffer).await?;
+
+            if stdout.read_until(b'\n', &mut buffer).await? == 0 {
+                break;
+            };
+
+            if stdout.read_until(b'\n', &mut buffer).await? == 0 {
+                break;
+            };
+
             let header = std::str::from_utf8(&buffer)?;
             let message_len: usize = header
                 .strip_prefix(CONTENT_LEN_HEADER)
@@ -378,6 +385,8 @@ impl LanguageServer {
             // Don't starve the main thread when receiving lots of messages at once.
             smol::future::yield_now().await;
         }
+
+        Ok(())
     }
 
     async fn handle_stderr<Stderr>(
@@ -393,7 +402,12 @@ impl LanguageServer {
 
         loop {
             buffer.clear();
-            stderr.read_until(b'\n', &mut buffer).await?;
+
+            let bytes_read = stderr.read_until(b'\n', &mut buffer).await?;
+            if bytes_read == 0 {
+                return Ok(());
+            }
+
             if let Ok(message) = str::from_utf8(&buffer) {
                 log::trace!("incoming stderr message:{message}");
                 for handler in io_handlers.lock().values_mut() {