From 54071e308d5f27a8353456e14571ae422c3f4a9f Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Mon, 8 Jul 2024 15:37:39 +1000 Subject: [PATCH 1/3] add LruSessionCache type --- sway-lsp/src/server_state.rs | 62 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/sway-lsp/src/server_state.rs b/sway-lsp/src/server_state.rs index 4415f0b2d91..e1047f6c1a1 100644 --- a/sway-lsp/src/server_state.rs +++ b/sway-lsp/src/server_state.rs @@ -14,8 +14,8 @@ use dashmap::DashMap; use forc_pkg::manifest::GenericManifestFile; use forc_pkg::PackageManifestFile; use lsp_types::{Diagnostic, Url}; -use parking_lot::RwLock; -use std::{collections::BTreeMap, process::Command}; +use parking_lot::{Mutex, RwLock}; +use std::{collections::{BTreeMap, VecDeque}, process::Command}; use std::{ mem, path::PathBuf, @@ -371,3 +371,61 @@ impl ServerState { Ok(session) } } + +/// A Least Recently Used (LRU) cache for storing and managing `Session` objects. +/// This cache helps limit memory usage by maintaining a fixed number of active sessions. +struct LruSessionCache { + /// Stores the actual `Session` objects, keyed by their file paths. + sessions: Arc>>, + /// Keeps track of the order in which sessions were accessed, with most recent at the front. + usage_order: Arc>>, + /// The maximum number of sessions that can be stored in the cache. + capacity: usize, +} + +impl LruSessionCache { + /// Creates a new `LruSessionCache` with the specified capacity. + fn new(capacity: usize) -> Self { + LruSessionCache { + sessions: Arc::new(DashMap::new()), + usage_order: Arc::new(Mutex::new(VecDeque::with_capacity(capacity))), + capacity, + } + } + + /// Retrieves a session from the cache and updates its position to the front of the usage order. + fn get(&self, path: &PathBuf) -> Option> { + if let Some(session) = self.sessions.get(path) { + self.move_to_front(path); + Some(session.clone()) + } else { + None + } + } + + /// Inserts a new session into the cache, evicting the least recently used session if at capacity. + fn insert(&self, path: PathBuf, session: Arc) { + if self.sessions.len() >= self.capacity { + self.evict_least_used(); + } + self.sessions.insert(path.clone(), session); + self.move_to_front(&path); + } + + /// Moves the specified path to the front of the usage order, marking it as most recently used. + fn move_to_front(&self, path: &PathBuf) { + let mut order = self.usage_order.lock(); + if let Some(index) = order.iter().position(|p| p == path) { + order.remove(index); + } + order.push_front(path.clone()); + } + + /// Removes the least recently used session from the cache when the capacity is reached. + fn evict_least_used(&self) { + let mut order = self.usage_order.lock(); + if let Some(old_path) = order.pop_back() { + self.sessions.remove(&old_path); + } + } +} \ No newline at end of file From d5bd949b2db587dc3e650735eff10a6cbadaa035 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Mon, 8 Jul 2024 15:58:21 +1000 Subject: [PATCH 2/3] use session cache in server_state --- sway-lsp/src/server_state.rs | 46 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/sway-lsp/src/server_state.rs b/sway-lsp/src/server_state.rs index e1047f6c1a1..4a0e475f06d 100644 --- a/sway-lsp/src/server_state.rs +++ b/sway-lsp/src/server_state.rs @@ -10,12 +10,15 @@ use crate::{ utils::{debug, keyword_docs::KeywordDocs}, }; use crossbeam_channel::{Receiver, Sender}; -use dashmap::DashMap; +use dashmap::{mapref::multiple::RefMulti, DashMap}; use forc_pkg::manifest::GenericManifestFile; use forc_pkg::PackageManifestFile; use lsp_types::{Diagnostic, Url}; use parking_lot::{Mutex, RwLock}; -use std::{collections::{BTreeMap, VecDeque}, process::Command}; +use std::{ + collections::{BTreeMap, VecDeque}, + process::Command, +}; use std::{ mem, path::PathBuf, @@ -28,13 +31,17 @@ use sway_core::LspConfig; use tokio::sync::Notify; use tower_lsp::{jsonrpc, Client}; +const DEFAULT_SESSION_CACHE_CAPACITY: usize = 4; + /// `ServerState` is the primary mutable state of the language server pub struct ServerState { pub(crate) client: Option, pub config: Arc>, pub(crate) keyword_docs: Arc, - /// A collection of [Session]s, each of which represents a project that has been opened in the users workspace - pub(crate) sessions: Arc>>, + /// A Least Recently Used (LRU) cache of [Session]s, each representing a project opened in the user's workspace. + /// This cache limits memory usage by maintaining a fixed number of active sessions, automatically + /// evicting the least recently used sessions when the capacity is reached. + pub(crate) sessions: LruSessionCache, pub documents: Documents, // Compilation thread related fields pub(crate) retrigger_compilation: Arc, @@ -52,7 +59,7 @@ impl Default for ServerState { client: None, config: Arc::new(RwLock::new(Config::default())), keyword_docs: Arc::new(KeywordDocs::new()), - sessions: Arc::new(DashMap::new()), + sessions: LruSessionCache::new(DEFAULT_SESSION_CACHE_CAPACITY), documents: Documents::new(), retrigger_compilation: Arc::new(AtomicBool::new(false)), is_compiling: Arc::new(AtomicBool::new(false)), @@ -357,24 +364,20 @@ impl ServerState { .ok_or(DirectoryError::ManifestDirNotFound)? .to_path_buf(); - let session = if let Some(item) = self.sessions.try_get(&manifest_dir).try_unwrap() { - item.value().clone() - } else { + let session = self.sessions.get(&manifest_dir).unwrap_or( { // If no session can be found, then we need to call init and insert a new session into the map self.init_session(uri).await?; self.sessions - .try_get(&manifest_dir) - .try_unwrap() - .map(|item| item.value().clone()) + .get(&manifest_dir) .expect("no session found even though it was just inserted into the map") - }; + }); Ok(session) } } /// A Least Recently Used (LRU) cache for storing and managing `Session` objects. /// This cache helps limit memory usage by maintaining a fixed number of active sessions. -struct LruSessionCache { +pub(crate) struct LruSessionCache { /// Stores the actual `Session` objects, keyed by their file paths. sessions: Arc>>, /// Keeps track of the order in which sessions were accessed, with most recent at the front. @@ -385,7 +388,7 @@ struct LruSessionCache { impl LruSessionCache { /// Creates a new `LruSessionCache` with the specified capacity. - fn new(capacity: usize) -> Self { + pub(crate) fn new(capacity: usize) -> Self { LruSessionCache { sessions: Arc::new(DashMap::new()), usage_order: Arc::new(Mutex::new(VecDeque::with_capacity(capacity))), @@ -393,9 +396,13 @@ impl LruSessionCache { } } + pub(crate) fn iter(&self) -> impl Iterator>> { + self.sessions.iter() + } + /// Retrieves a session from the cache and updates its position to the front of the usage order. - fn get(&self, path: &PathBuf) -> Option> { - if let Some(session) = self.sessions.get(path) { + pub(crate) fn get(&self, path: &PathBuf) -> Option> { + if let Some(session) = self.sessions.try_get(path).try_unwrap() { self.move_to_front(path); Some(session.clone()) } else { @@ -404,7 +411,8 @@ impl LruSessionCache { } /// Inserts a new session into the cache, evicting the least recently used session if at capacity. - fn insert(&self, path: PathBuf, session: Arc) { + pub(crate) fn insert(&self, path: PathBuf, session: Arc) { + tracing::trace!("Inserting new session for path: {:?}", path); if self.sessions.len() >= self.capacity { self.evict_least_used(); } @@ -414,6 +422,7 @@ impl LruSessionCache { /// Moves the specified path to the front of the usage order, marking it as most recently used. fn move_to_front(&self, path: &PathBuf) { + tracing::trace!("Moving path to front of usage order: {:?}", path); let mut order = self.usage_order.lock(); if let Some(index) = order.iter().position(|p| p == path) { order.remove(index); @@ -425,7 +434,8 @@ impl LruSessionCache { fn evict_least_used(&self) { let mut order = self.usage_order.lock(); if let Some(old_path) = order.pop_back() { + tracing::trace!("Cache at capacity. Evicting least used session: {:?}", old_path); self.sessions.remove(&old_path); } } -} \ No newline at end of file +} From 0b77942c9f5740e7be377b78ca68b9b4a433456a Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Mon, 8 Jul 2024 16:06:33 +1000 Subject: [PATCH 3/3] fmt --- sway-lsp/src/server_state.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sway-lsp/src/server_state.rs b/sway-lsp/src/server_state.rs index 4a0e475f06d..c594ba96854 100644 --- a/sway-lsp/src/server_state.rs +++ b/sway-lsp/src/server_state.rs @@ -364,7 +364,7 @@ impl ServerState { .ok_or(DirectoryError::ManifestDirNotFound)? .to_path_buf(); - let session = self.sessions.get(&manifest_dir).unwrap_or( { + let session = self.sessions.get(&manifest_dir).unwrap_or({ // If no session can be found, then we need to call init and insert a new session into the map self.init_session(uri).await?; self.sessions @@ -434,7 +434,10 @@ impl LruSessionCache { fn evict_least_used(&self) { let mut order = self.usage_order.lock(); if let Some(old_path) = order.pop_back() { - tracing::trace!("Cache at capacity. Evicting least used session: {:?}", old_path); + tracing::trace!( + "Cache at capacity. Evicting least used session: {:?}", + old_path + ); self.sessions.remove(&old_path); } }