diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 2d716f8e519..d40ea8fd219 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -530,9 +530,18 @@ pub struct RealFs { pub trait FileHandle: Send + Sync + std::fmt::Debug { fn current_path(&self, fs: &Arc) -> Result; + + fn debug_fd(&self) -> Option { + None + } } impl FileHandle for std::fs::File { + #[cfg(unix)] + fn debug_fd(&self) -> Option { + Some(self.as_raw_fd().into()) + } + #[cfg(target_os = "macos")] fn current_path(&self, _: &Arc) -> Result { use std::{ diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index f9076753998..a315eef6613 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -12,7 +12,8 @@ use gpui::{ WeakEntity, }; use language::{ - Buffer, BufferEvent, Capability, DiskState, File as _, Language, LineEnding, Operation, + Buffer, BufferEvent, Capability, DiskState, File as _, Language, LineEnding, LocalFile as _, + Operation, language_settings::{AllLanguageSettings, LineEndingSetting}, proto::{ deserialize_line_ending, deserialize_version, serialize_line_ending, serialize_version, @@ -963,7 +964,21 @@ impl BufferStore { path: file.path.clone(), worktree_id: file.worktree_id(cx), }); + let file = File::from_dyn(buffer.file()).cloned(); let is_remote = buffer.replica_id().is_remote(); + log::debug!( + "adding buffer entity_id={:?}, buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={:?}, worktree_single_file={:?}, root_file_handle_fd={:?}", + buffer_entity.entity_id(), + remote_id, + file.as_ref().map(|file| file.abs_path(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file.as_ref() + .map(|file| file.worktree.read(cx).is_visible()), + file.as_ref() + .map(|file| file.worktree.read(cx).is_single_file()), + file.as_ref() + .and_then(|file| file.worktree.read(cx).root_file_handle_debug_fd()) + ); let open_buffer = OpenBuffer::Complete { buffer: buffer_entity.downgrade(), }; @@ -971,6 +986,17 @@ impl BufferStore { let handle = cx.entity().downgrade(); buffer_entity.update(cx, move |_, cx| { cx.on_release(move |buffer, cx| { + let file = File::from_dyn(buffer.file()).cloned(); + log::debug!( + "buffer released buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={:?}, worktree_single_file={:?}, root_file_handle_fd={:?}", + buffer.remote_id(), + file.as_ref().map(|file| file.abs_path(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file.as_ref().map(|file| file.worktree.read(cx).is_visible()), + file.as_ref().map(|file| file.worktree.read(cx).is_single_file()), + file.as_ref() + .and_then(|file| file.worktree.read(cx).root_file_handle_debug_fd()) + ); handle .update(cx, |_, cx| { cx.emit(BufferStoreEvent::BufferDropped(buffer.remote_id())) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 85229cfdcde..bb45dcf2998 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2784,6 +2784,15 @@ impl LocalLspStore { let Some(file) = File::from_dyn(buffer.file()) else { return; }; + log::debug!( + "registering buffer with language servers buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={}, worktree_single_file={}, root_file_handle_fd={:?}", + buffer_id, + file.abs_path(cx), + file.worktree_id(cx), + file.worktree.read(cx).is_visible(), + file.worktree.read(cx).is_single_file(), + file.worktree.read(cx).root_file_handle_debug_fd() + ); if !file.is_local() { return; } @@ -2883,6 +2892,13 @@ impl LocalLspStore { } }) .collect::>(); + log::debug!( + "selected language servers for buffer buffer_id={:?}, path={:?}, server_count={}, reused={}", + buffer_id, + abs_path, + servers_and_adapters.len(), + reused + ); for (server, adapter) in servers_and_adapters { buffer_handle.update(cx, |buffer, cx| { buffer.set_completion_triggers( @@ -3027,6 +3043,18 @@ impl LocalLspStore { cx: &mut App, ) { buffer.update(cx, |buffer, cx| { + let file = File::from_dyn(buffer.file()).cloned(); + log::debug!( + "unregistering buffer from language servers buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={:?}, worktree_single_file={:?}, root_file_handle_fd={:?}, uri={}", + buffer.remote_id(), + file.as_ref().map(|file| file.abs_path(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file.as_ref().map(|file| file.worktree.read(cx).is_visible()), + file.as_ref().map(|file| file.worktree.read(cx).is_single_file()), + file.as_ref() + .and_then(|file| file.worktree.read(cx).root_file_handle_debug_fd()), + file_url + ); let mut snapshots = self.buffer_snapshots.remove(&buffer.remote_id()); for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { @@ -4614,6 +4642,21 @@ impl LspStore { ) -> OpenLspBufferHandle { let buffer_id = buffer.read(cx).remote_id(); let handle = OpenLspBufferHandle(cx.new(|_| OpenLspBuffer(buffer.clone()))); + let file = File::from_dyn(buffer.read(cx).file()).cloned(); + log::debug!( + "creating open lsp buffer handle entity_id={:?}, buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={:?}, worktree_single_file={:?}, root_file_handle_fd={:?}, ignore_refcounts={}", + handle.0.entity_id(), + buffer_id, + file.as_ref().map(|file| file.abs_path(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file.as_ref() + .map(|file| file.worktree.read(cx).is_visible()), + file.as_ref() + .map(|file| file.worktree.read(cx).is_single_file()), + file.as_ref() + .and_then(|file| file.worktree.read(cx).root_file_handle_debug_fd()), + ignore_refcounts + ); if let Some(local) = self.as_local_mut() { let refcount = local.registered_buffers.entry(buffer_id).or_insert(0); if !ignore_refcounts { @@ -4636,6 +4679,18 @@ impl LspStore { } if !ignore_refcounts { cx.observe_release(&handle.0, move |lsp_store, buffer, cx| { + let file = File::from_dyn(buffer.0.read(cx).file()).cloned(); + log::debug!( + "open lsp buffer handle released entity_id={:?}, buffer_id={:?}, path={:?}, worktree_id={:?}, worktree_visible={:?}, worktree_single_file={:?}, root_file_handle_fd={:?}", + buffer.0.entity_id(), + buffer_id, + file.as_ref().map(|file| file.abs_path(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file.as_ref().map(|file| file.worktree.read(cx).is_visible()), + file.as_ref().map(|file| file.worktree.read(cx).is_single_file()), + file.as_ref() + .and_then(|file| file.worktree.read(cx).root_file_handle_debug_fd()) + ); let refcount = { let local = lsp_store.as_local_mut().unwrap(); let Some(refcount) = local.registered_buffers.get_mut(&buffer_id) else { @@ -4646,6 +4701,11 @@ impl LspStore { *refcount -= 1; *refcount }; + log::debug!( + "open lsp buffer handle refcount after release buffer_id={:?}, refcount={}", + buffer_id, + refcount + ); if refcount == 0 { lsp_store.lsp_data.remove(&buffer_id); lsp_store.buffer_reload_tasks.remove(&buffer_id); diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index f544973a548..5d95c1ebfca 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -888,6 +888,16 @@ impl WorktreeStore { } else { WorktreeHandle::Weak(worktree.downgrade()) }; + log::debug!( + "adding worktree entity_id={:?}, worktree_id={:?}, path={:?}, visible={}, single_file={}, retained_strongly={}, root_file_handle_fd={:?}", + worktree.entity_id(), + worktree_id, + worktree.read(cx).abs_path(), + worktree.read(cx).is_visible(), + worktree.read(cx).is_single_file(), + push_strong_handle, + worktree.read(cx).root_file_handle_debug_fd() + ); self.worktrees.push(handle); cx.emit(WorktreeStoreEvent::WorktreeAdded(worktree.clone())); @@ -925,6 +935,15 @@ impl WorktreeStore { }) .detach(); cx.observe_release(worktree, move |this, worktree, cx| { + log::debug!( + "worktree released entity_id={:?}, worktree_id={:?}, path={:?}, visible={}, single_file={}, root_file_handle_fd={:?}", + handle_id, + worktree.id(), + worktree.abs_path(), + worktree.is_visible(), + worktree.is_single_file(), + worktree.root_file_handle_debug_fd() + ); cx.emit(WorktreeStoreEvent::WorktreeReleased( handle_id, worktree.id(), @@ -942,6 +961,15 @@ impl WorktreeStore { self.worktrees.retain(|worktree| { if let Some(worktree) = worktree.upgrade() { if worktree.read(cx).id() == id_to_remove { + log::debug!( + "removing worktree entity_id={:?}, worktree_id={:?}, path={:?}, visible={}, single_file={}, root_file_handle_fd={:?}", + worktree.entity_id(), + id_to_remove, + worktree.read(cx).abs_path(), + worktree.read(cx).is_visible(), + worktree.read(cx).is_single_file(), + worktree.read(cx).root_file_handle_debug_fd() + ); cx.emit(WorktreeStoreEvent::WorktreeRemoved( worktree.entity_id(), id_to_remove, diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index a21ea8f639e..15df4cdfb98 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -57,6 +57,7 @@ use std::{ future::Future, mem::{self}, ops::{Deref, DerefMut, Range}, + panic::Location, path::{Path, PathBuf}, pin::Pin, sync::{ @@ -150,6 +151,7 @@ pub struct PathPrefixScanRequest { struct ScanRequest { relative_paths: Vec>, + trigger_locations: SmallVec<[&'static Location<'static>; 1]>, done: SmallVec<[barrier::Sender; 1]>, } @@ -258,6 +260,34 @@ pub struct LocalSnapshot { root_file_handle: Option>, } +#[derive(Debug)] +struct RootFileHandle { + inner: Arc, + path: Arc, + worktree_id: WorktreeId, +} + +impl fs::FileHandle for RootFileHandle { + fn current_path(&self, fs: &Arc) -> Result { + self.inner.current_path(fs) + } + + fn debug_fd(&self) -> Option { + self.inner.debug_fd() + } +} + +impl Drop for RootFileHandle { + fn drop(&mut self) { + log::debug!( + "dropping root file handle worktree_id={:?}, path={:?}, root_file_handle_fd={:?}", + self.worktree_id, + self.path, + self.inner.debug_fd() + ); + } +} + struct BackgroundScannerState { snapshot: LocalSnapshot, symlink_paths_by_target: HashMap, SmallVec<[Arc; 1]>>, @@ -411,9 +441,26 @@ impl Worktree { ) }) .log_err() + .map(|inner| { + Arc::new(RootFileHandle { + inner, + path: abs_path.clone(), + worktree_id, + }) as Arc + }) } else { None }; + log::debug!( + "created local worktree root handle path={:?}, visible={}, is_single_file={}, root_file_handle_fd={:?}, worktree_id={:?}", + abs_path, + visible, + metadata.as_ref().is_some_and(|metadata| !metadata.is_dir), + root_file_handle + .as_ref() + .and_then(|handle| handle.debug_fd()), + worktree_id + ); let root_repo_common_dir = if visible { discover_root_repo_common_dir(&abs_path, fs.as_ref()) @@ -739,6 +786,13 @@ impl Worktree { } } + pub fn root_file_handle_debug_fd(&self) -> Option { + match self { + Worktree::Local(worktree) => worktree.root_file_handle_debug_fd(), + Worktree::Remote(_) => None, + } + } + pub fn root_file(&self, cx: &Context) -> Option> { let entry = self.root_entry()?; Some(File::for_entry(entry.clone(), cx.entity())) @@ -1111,11 +1165,31 @@ impl Worktree { } } +impl Drop for LocalWorktree { + fn drop(&mut self) { + log::debug!( + "dropping local worktree worktree_id={:?}, path={:?}, visible={}, single_file={}, root_file_handle_fd={:?}", + self.snapshot.id(), + self.snapshot.abs_path, + self.visible, + self.snapshot.root_dir().is_none(), + self.root_file_handle_debug_fd() + ); + } +} + impl LocalWorktree { pub fn fs(&self) -> &Arc { &self.fs } + pub fn root_file_handle_debug_fd(&self) -> Option { + self.snapshot + .root_file_handle + .as_ref() + .and_then(|handle| handle.debug_fd()) + } + pub fn is_path_private(&self, path: &RelPath) -> bool { !self.share_private_files && self.settings.is_path_private(path) } @@ -1924,11 +1998,13 @@ impl LocalWorktree { })) } + #[track_caller] pub fn refresh_entries_for_paths(&self, paths: Vec>) -> barrier::Receiver { let (tx, rx) = barrier::channel(); self.scan_requests_tx .try_send(ScanRequest { relative_paths: paths, + trigger_locations: smallvec![Location::caller()], done: smallvec![tx], }) .ok(); @@ -4210,7 +4286,24 @@ impl BackgroundScanner { } async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool { - log::debug!("rescanning paths {:?}", request.relative_paths); + let trigger_locations = request + .trigger_locations + .iter() + .map(|location| { + format!( + "{}:{}:{}", + location.file(), + location.line(), + location.column() + ) + }) + .collect::>(); + log::debug!( + "rescanning paths {:?}, requested from {:?}, single_file_worktree={}", + request.relative_paths, + trigger_locations, + self.is_single_file + ); request.relative_paths.sort_unstable(); self.forcibly_load_paths(&request.relative_paths).await; @@ -5572,6 +5665,9 @@ impl BackgroundScanner { let mut request = self.scan_requests_rx.recv().await?; while let Ok(next_request) = self.scan_requests_rx.try_recv() { request.relative_paths.extend(next_request.relative_paths); + request + .trigger_locations + .extend(next_request.trigger_locations); request.done.extend(next_request.done); } Ok(request)