diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index ea8a6975195..5024e04ffd3 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -12,12 +12,12 @@ use load_cargo::SourceRootConfig; use lsp_types::{SemanticTokens, Url}; use nohash_hasher::IntMap; -use parking_lot::{Mutex, RwLock}; +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard}; use proc_macro_api::ProcMacroServer; use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts}; use rustc_hash::{FxHashMap, FxHashSet}; use triomphe::Arc; -use vfs::AnchoredPathBuf; +use vfs::{AnchoredPathBuf, Vfs}; use crate::{ config::{Config, ConfigError}, @@ -216,12 +216,15 @@ pub(crate) fn process_changes(&mut self) -> bool { let mut file_changes = FxHashMap::default(); let (change, changed_files, workspace_structure_change) = { let mut change = Change::new(); - let (vfs, line_endings_map) = &mut *self.vfs.write(); - let changed_files = vfs.take_changes(); + let mut guard = self.vfs.write(); + let changed_files = guard.0.take_changes(); if changed_files.is_empty() { return false; } + // downgrade to read lock to allow more readers while we are normalizing text + let guard = RwLockWriteGuard::downgrade_to_upgradable(guard); + let vfs: &Vfs = &guard.0; // We need to fix up the changed events a bit. If we have a create or modify for a file // id that is followed by a delete we actually skip observing the file text from the // earlier event, to avoid problems later on. @@ -272,6 +275,7 @@ pub(crate) fn process_changes(&mut self) -> bool { let mut workspace_structure_change = None; // A file was added or deleted let mut has_structure_changes = false; + let mut bytes = vec![]; for file in &changed_files { let vfs_path = &vfs.file_path(file.file_id); if let Some(path) = vfs_path.as_path() { @@ -293,16 +297,28 @@ pub(crate) fn process_changes(&mut self) -> bool { let text = if file.exists() { let bytes = vfs.file_contents(file.file_id).to_vec(); + String::from_utf8(bytes).ok().and_then(|text| { + // FIXME: Consider doing normalization in the `vfs` instead? That allows + // getting rid of some locking let (text, line_endings) = LineEndings::normalize(text); - line_endings_map.insert(file.file_id, line_endings); - Some(Arc::from(text)) + Some((Arc::from(text), line_endings)) }) } else { None }; - change.change_file(file.file_id, text); + // delay `line_endings_map` changes until we are done normalizing the text + // this allows delaying the re-acquisition of the write lock + bytes.push((file.file_id, text)); } + let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard); + bytes.into_iter().for_each(|(file_id, text)| match text { + None => change.change_file(file_id, None), + Some((text, line_endings)) => { + line_endings_map.insert(file_id, line_endings); + change.change_file(file_id, Some(text)); + } + }); if has_structure_changes { let roots = self.source_root_config.partition(vfs); change.set_roots(roots); diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index e830e5e9a64..3fd10274e0d 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -84,15 +84,16 @@ pub(crate) fn handle_did_change_text_document( } }; - let vfs = &mut state.vfs.write().0; - let file_id = vfs.file_id(&path).unwrap(); let text = apply_document_changes( state.config.position_encoding(), - || std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(), + || { + let vfs = &state.vfs.read().0; + let file_id = vfs.file_id(&path).unwrap(); + std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into() + }, params.content_changes, ); - - vfs.set_file_contents(path, Some(text.into_bytes())); + state.vfs.write().0.set_file_contents(path, Some(text.into_bytes())); } Ok(()) }