language: Defer dropping the `SyntaxSnapshot` to a background thread (#50386)

Lukas Wirth created

Dropping deep tree-sitter Trees can be quite slow due to deallocating
lots of memory (in the 10s of milliseconds for big diffs). To avoid
blocking the main thread, we offload the drop operation to a background
thread.

Instead of a static thread we could also integrate this with the gpui
app or use the executors, but both of that would require threading that
through as a field which I don't think is too great either

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/language/src/syntax_map.rs | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)

Detailed changes

crates/language/src/syntax_map.rs 🔗

@@ -13,7 +13,7 @@ use std::{
     collections::BinaryHeap,
     fmt, iter,
     ops::{ControlFlow, Deref, DerefMut, Range},
-    sync::Arc,
+    sync::{Arc, LazyLock},
     time::{Duration, Instant},
 };
 use streaming_iterator::StreamingIterator;
@@ -40,6 +40,27 @@ pub struct SyntaxSnapshot {
     update_count: usize,
 }
 
+// Dropping deep treesitter Trees can be quite slow due to deallocating lots of memory.
+// To avoid blocking the main thread, we offload the drop operation to a background thread.
+impl Drop for SyntaxSnapshot {
+    fn drop(&mut self) {
+        static DROP_TX: LazyLock<std::sync::mpsc::Sender<SumTree<SyntaxLayerEntry>>> =
+            LazyLock::new(|| {
+                let (tx, rx) = std::sync::mpsc::channel();
+                std::thread::Builder::new()
+                    .name("SyntaxSnapshot::drop".into())
+                    .spawn(move || while let Ok(_) = rx.recv() {})
+                    .expect("failed to spawn drop thread");
+                tx
+            });
+        // This does allocate a new Arc, but it's cheap and avoids blocking the main thread without needing to use an `Option` or `MaybeUninit`.
+        let _ = DROP_TX.send(std::mem::replace(
+            &mut self.layers,
+            SumTree::from_summary(Default::default()),
+        ));
+    }
+}
+
 #[derive(Default)]
 pub struct SyntaxMapCaptures<'a> {
     layers: Vec<SyntaxMapCapturesLayer<'a>>,