Cancel language server requests if request is dropped (#4159)

Thorsten Ball created

Before this change, we would send requests to language servers without
canceling them even if we never wait for their response.

Example: when doing document highlights, we'd send a request (modulo
debouncing) whenever a change was made, ignoring previously sent
requests that might still be in-flight.

With this change, we now send a Cancel request (from the LSP spec) to
the language server in case no one listens to the response anymore
(which is what happens when the `Future` returned by `request_internal`)
is dropped.

Release Notes:

- Improved performance when interacting with language servers.

Change summary

crates/lsp/src/lsp.rs   | 14 ++++++++++++++
crates/util/src/util.rs |  2 +-
2 files changed, 15 insertions(+), 1 deletion(-)

Detailed changes

crates/lsp/src/lsp.rs 🔗

@@ -863,17 +863,31 @@ impl LanguageServer {
             .try_send(message)
             .context("failed to write to language server's stdin");
 
+        let outbound_tx = outbound_tx.downgrade();
         let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse();
         let started = Instant::now();
         async move {
             handle_response?;
             send?;
 
+            let cancel_on_drop = util::defer(move || {
+                if let Some(outbound_tx) = outbound_tx.upgrade() {
+                    Self::notify_internal::<notification::Cancel>(
+                        &outbound_tx,
+                        CancelParams {
+                            id: NumberOrString::Number(id as i32),
+                        },
+                    )
+                    .log_err();
+                }
+            });
+
             let method = T::METHOD;
             futures::select! {
                 response = rx.fuse() => {
                     let elapsed = started.elapsed();
                     log::trace!("Took {elapsed:?} to receive response to {method:?} id {id}");
+                    cancel_on_drop.abort();
                     response?
                 }
 

crates/util/src/util.rs 🔗

@@ -299,7 +299,7 @@ pub struct Deferred<F: FnOnce()>(Option<F>);
 
 impl<F: FnOnce()> Deferred<F> {
     /// Drop without running the deferred function.
-    pub fn cancel(mut self) {
+    pub fn abort(mut self) {
         self.0.take();
     }
 }