Cancel language server requests if request is dropped

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.

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();
     }
 }