From 11df3a0dec58b3210bf858c200ef4ba60a7157fd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 28 Dec 2025 12:41:09 +0100 Subject: [PATCH] Drop AstIdMap asynchronously --- .../rust-analyzer/crates/span/src/ast_id.rs | 42 +++++++++++++++++++ .../rust-analyzer/crates/span/src/map.rs | 33 ++++----------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index 3bf14dea75b3..da537ba9900c 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -777,6 +777,48 @@ impl AstIdMap { } } +#[cfg(not(no_salsa_async_drops))] +impl Drop for AstIdMap { + fn drop(&mut self) { + let arena = std::mem::take(&mut self.arena); + let ptr_map = std::mem::take(&mut self.ptr_map); + let id_map = std::mem::take(&mut self.id_map); + static AST_ID_MAP_DROP_THREAD: std::sync::OnceLock< + std::sync::mpsc::Sender<( + Arena<(SyntaxNodePtr, ErasedFileAstId)>, + hashbrown::HashTable, + hashbrown::HashTable, + )>, + > = std::sync::OnceLock::new(); + AST_ID_MAP_DROP_THREAD + .get_or_init(|| { + let (sender, receiver) = std::sync::mpsc::channel::<( + Arena<(SyntaxNodePtr, ErasedFileAstId)>, + hashbrown::HashTable, + hashbrown::HashTable, + )>(); + std::thread::Builder::new() + .name("AstIdMapDropper".to_owned()) + .spawn(move || { + loop { + // block on a receive + _ = receiver.recv(); + // then drain the entire channel + while let Ok(_) = receiver.try_recv() {} + // and sleep for a bit + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // why do this over just a `receiver.iter().for_each(drop)`? To reduce contention on the channel lock. + // otherwise this thread will constantly wake up and sleep again. + }) + .unwrap(); + sender + }) + .send((arena, ptr_map, id_map)) + .unwrap(); + } +} + #[inline] fn hash_ptr(ptr: &SyntaxNodePtr) -> u64 { FxBuildHasher.hash_one(ptr) diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs index f5c083a9f650..75a1b0029cbc 100644 --- a/src/tools/rust-analyzer/crates/span/src/map.rs +++ b/src/tools/rust-analyzer/crates/span/src/map.rs @@ -153,26 +153,22 @@ impl SpanMap { #[cfg(not(no_salsa_async_drops))] impl Drop for SpanMap { fn drop(&mut self) { - struct SendPtr(*mut [()]); - unsafe impl Send for SendPtr {} + let spans = std::mem::take(&mut self.spans); static SPAN_MAP_DROP_THREAD: std::sync::OnceLock< - std::sync::mpsc::Sender<(SendPtr, fn(SendPtr))>, + std::sync::mpsc::Sender>, > = std::sync::OnceLock::new(); + SPAN_MAP_DROP_THREAD .get_or_init(|| { - let (sender, receiver) = std::sync::mpsc::channel::<(SendPtr, fn(SendPtr))>(); + let (sender, receiver) = std::sync::mpsc::channel::>(); std::thread::Builder::new() .name("SpanMapDropper".to_owned()) .spawn(move || { loop { // block on a receive - if let Ok((b, drop)) = receiver.recv() { - drop(b); - } + _ = receiver.recv(); // then drain the entire channel - while let Ok((b, drop)) = receiver.try_recv() { - drop(b); - } + while let Ok(_) = receiver.try_recv() {} // and sleep for a bit std::thread::sleep(std::time::Duration::from_millis(100)); } @@ -182,22 +178,7 @@ impl Drop for SpanMap { .unwrap(); sender }) - .send(( - unsafe { - SendPtr(std::mem::transmute::<*mut [(TextSize, Span)], *mut [()]>(Box::< - [(TextSize, Span)], - >::into_raw( - std::mem::take(&mut self.spans).into_boxed_slice(), - ))) - }, - |b: SendPtr| { - _ = unsafe { - Box::from_raw(std::mem::transmute::<*mut [()], *mut [(TextSize, Span)]>( - b.0, - )) - } - }, - )) + .send(spans) .unwrap(); } }