From b3338a0bf0aa79bb303784233da7406c04de08da Mon Sep 17 00:00:00 2001 From: Jonatan Waern Date: Tue, 30 Sep 2025 14:03:25 +0200 Subject: [PATCH 1/2] Cache path resolver, invalidating as necessary Signed-off-by: Jonatan Waern --- CHANGELOG.md | 3 ++- src/actions/mod.rs | 23 ++++++++++++++++++++--- src/actions/notifications.rs | 4 ++-- src/server/mod.rs | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5990f..cd3d7df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ # Change Log # 0.9.15 - +- Slight optimization in how the server resolves file paths, should reduce + time-to-ready for the server when first starting by about 30%. ## 0.9.14 - Slight optimization to the memory usage of device-level analysis which diff --git a/src/actions/mod.rs b/src/actions/mod.rs index b14d306..5f41df0 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -263,6 +263,8 @@ pub struct InitActionContext { // the root workspaces pub workspace_roots: Arc>>, + pub cached_path_resolver: Arc>>, + // directly opened files pub direct_opens: Arc>>, pub compilation_info: Arc>, @@ -378,6 +380,7 @@ impl InitActionContext { config, lint_config: Arc::new(Mutex::new(LintCfg::default())), jobs: Arc::default(), + cached_path_resolver: Arc::default(), direct_opens: Arc::default(), quiescent: Arc::new(AtomicBool::new(false)), prev_changes: Arc::default(), @@ -419,12 +422,17 @@ impl InitActionContext { pub fn update_workspaces(&self, mut add: Vec, - remove: Vec) { + remove: Vec, + out: &O) { + let any_change = !(add.is_empty() && remove.is_empty()); if let Ok(mut workspaces) = self.workspace_roots.lock() { workspaces.retain(|workspace| remove.iter().all(|rem|rem != workspace)); workspaces.append(&mut add); } + if any_change { + self.update_compilation_info(out); + } } fn update_linter_config(&self, out: &O) { @@ -445,6 +453,11 @@ impl InitActionContext { trace!("Updating compile info"); if let Ok(config) = self.config.lock() { if let Some(compile_info) = &config.compile_info_path { + // Ensure resolver exists + self.construct_resolver(); + // And then remove it from storage (invalidates it) + let old_resolver = self.cached_path_resolver.lock() + .expect("Failed to grab resolver").take().unwrap(); if let Some(canon_path) = CanonPath::from_path_buf( compile_info.clone()) { let workspaces = self.workspace_roots.lock().unwrap(); @@ -468,8 +481,7 @@ impl InitActionContext { *ci = compilation_info; } self.analysis.lock().unwrap() - .update_all_context_dependencies( - self.construct_resolver()); + .update_all_context_dependencies(old_resolver); }, Err(e) => { error!("Failed to update compilation info: {}", e); @@ -776,6 +788,10 @@ impl InitActionContext { } pub fn construct_resolver(&self) -> PathResolver { + if let Some(resolver) = self.cached_path_resolver.lock() + .unwrap().as_ref() { + return resolver.clone(); + } trace!("About to construct resolver"); let mut toret: PathResolver = self.client_capabilities.root.clone().into(); @@ -788,6 +804,7 @@ impl InitActionContext { .into_iter().collect())) .collect()); trace!("Constructed resolver: {:?}", toret); + *self.cached_path_resolver.lock().unwrap() = Some(toret.clone()); toret } diff --git a/src/actions/notifications.rs b/src/actions/notifications.rs index a3f6927..a24cd0a 100644 --- a/src/actions/notifications.rs +++ b/src/actions/notifications.rs @@ -262,11 +262,11 @@ impl BlockingNotificationAction for DidChangeWorkspaceFolders { fn handle( params: DidChangeWorkspaceFoldersParams, ctx: &mut InitActionContext, - _out: O, + out: O, ) -> Result<(), ResponseError> { let added = params.event.added; let removed = params.event.removed; - ctx.update_workspaces(added, removed); + ctx.update_workspaces(added, removed, &out); Ok(()) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index c2ae378..7712ba3 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -270,7 +270,7 @@ impl BlockingRequestAction for InitializeRequest { }); } if let ActionContext::Init(ref mut initctx) = ctx { - initctx.update_workspaces(workspaces, vec![]); + initctx.update_workspaces(workspaces, vec![], &out); let temp_resolver = initctx.construct_resolver(); for file in IMPLICIT_IMPORTS { debug!("Requesting analysis of builtin file {}", file); From e85ba2ca783f096396e2cdd95c4ac843778e131f Mon Sep 17 00:00:00 2001 From: Jonatan Waern Date: Tue, 4 Nov 2025 09:41:16 +0100 Subject: [PATCH 2/2] Cache file resolver calls Signed-off-by: Jonatan Waern --- CHANGELOG.md | 5 +++-- src/file_management.rs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd3d7df..4688fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,9 @@ # Change Log # 0.9.15 -- Slight optimization in how the server resolves file paths, should reduce - time-to-ready for the server when first starting by about 30%. +- Optimizations in how the server resolves file paths, should reduce + time-to-ready for the server when first starting by about 50%, depending + on the complexity of the include tree. ## 0.9.14 - Slight optimization to the memory usage of device-level analysis which diff --git a/src/file_management.rs b/src/file_management.rs index b26b9c6..98c1b33 100644 --- a/src/file_management.rs +++ b/src/file_management.rs @@ -8,6 +8,7 @@ use std::fs; use std::path::{Path, PathBuf}; use std::ops::Deref; +use std::cell::RefCell; // A path which we know to be canonical in the filesystem #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -52,12 +53,14 @@ impl CanonPath { } /// This is how we resolve relative paths to in-workspace full paths -#[derive(Eq, PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub struct PathResolver { // Root is provided by context, who will pass this struct to threads for // import resolution roots: Vec, include_paths: HashMap>, + #[allow(clippy::type_complexity)] + cache: RefCell), Option>>, } impl From> for PathResolver { @@ -69,6 +72,7 @@ impl From> for PathResolver { PathResolver { roots, include_paths: HashMap::default(), + cache: RefCell::default(), } } } @@ -117,6 +121,16 @@ impl PathResolver { path: &Path, context: Option<&CanonPath>) -> Option { + self.cache.borrow_mut().entry((path.to_path_buf(), context.cloned())) + .or_insert_with( + ||self.resolve_with_maybe_context_impl(path, context)) + .clone() + } + + fn resolve_with_maybe_context_impl(&self, + path: &Path, + context: Option<&CanonPath>) + -> Option { // Given some relative info, find a canonical file path // NOTE: Right now the relative info is a pathbuf, but this might // change later