perf(lsp): don't clone asset text (#28165)

This commit is contained in:
Nayeem Rahman 2025-02-18 11:46:19 +00:00 committed by GitHub
parent 17a51c401a
commit f2a8c300d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 105 additions and 149 deletions

View file

@ -1999,7 +1999,6 @@ mod tests {
StateSnapshot { StateSnapshot {
project_version: 0, project_version: 0,
documents: Arc::new(documents), documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config), config: Arc::new(config),
resolver, resolver,
}, },

View file

@ -23,6 +23,8 @@ use deno_core::futures::future;
use deno_core::futures::future::Shared; use deno_core::futures::future::Shared;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
use deno_core::parking_lot::RwLock;
use deno_core::resolve_url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_error::JsErrorBox; use deno_error::JsErrorBox;
use deno_graph::Resolution; use deno_graph::Resolution;
@ -36,6 +38,7 @@ use indexmap::IndexSet;
use node_resolver::cache::NodeResolutionThreadLocalCache; use node_resolver::cache::NodeResolutionThreadLocalCache;
use node_resolver::NodeResolutionKind; use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode; use node_resolver::ResolutionMode;
use once_cell::sync::Lazy;
use tower_lsp::lsp_types as lsp; use tower_lsp::lsp_types as lsp;
use super::cache::calculate_fs_version; use super::cache::calculate_fs_version;
@ -48,8 +51,8 @@ use super::testing::TestCollector;
use super::testing::TestModule; use super::testing::TestModule;
use super::text::LineIndex; use super::text::LineIndex;
use super::tsc; use super::tsc;
use super::tsc::AssetDocument;
use crate::graph_util::CliJsrUrlProvider; use crate::graph_util::CliJsrUrlProvider;
use crate::tsc::MaybeStaticSource;
pub const DOCUMENT_SCHEMES: [&str; 5] = pub const DOCUMENT_SCHEMES: [&str; 5] =
["data", "blob", "file", "http", "https"]; ["data", "blob", "file", "http", "https"];
@ -180,10 +183,85 @@ impl IndexValid {
} }
} }
/// An lsp representation of an asset in memory, that has either been retrieved
/// from static assets built into Rust, or static assets built into tsc.
#[derive(Debug)]
pub struct AssetDocument {
specifier: ModuleSpecifier,
text: MaybeStaticSource,
line_index: Arc<LineIndex>,
maybe_navigation_tree: Mutex<Option<Arc<tsc::NavigationTree>>>,
}
impl AssetDocument {
pub fn new(specifier: ModuleSpecifier, text: MaybeStaticSource) -> Self {
let line_index = Arc::new(LineIndex::new(text.as_ref()));
Self {
specifier,
text,
line_index,
maybe_navigation_tree: Default::default(),
}
}
pub fn specifier(&self) -> &ModuleSpecifier {
&self.specifier
}
pub fn cache_navigation_tree(
&self,
navigation_tree: Arc<tsc::NavigationTree>,
) {
*self.maybe_navigation_tree.lock() = Some(navigation_tree);
}
pub fn text(&self) -> MaybeStaticSource {
self.text.clone()
}
pub fn line_index(&self) -> Arc<LineIndex> {
self.line_index.clone()
}
pub fn maybe_navigation_tree(&self) -> Option<Arc<tsc::NavigationTree>> {
self.maybe_navigation_tree.lock().clone()
}
}
#[derive(Debug)]
pub struct AssetDocuments {
inner: RwLock<HashMap<ModuleSpecifier, Arc<AssetDocument>>>,
}
impl AssetDocuments {
pub fn contains_key(&self, k: &ModuleSpecifier) -> bool {
self.inner.read().contains_key(k)
}
pub fn get(&self, k: &ModuleSpecifier) -> Option<Arc<AssetDocument>> {
self.inner.read().get(k).cloned()
}
}
pub static ASSET_DOCUMENTS: Lazy<AssetDocuments> =
Lazy::new(|| AssetDocuments {
inner: RwLock::new(
crate::tsc::LAZILY_LOADED_STATIC_ASSETS
.iter()
.map(|(k, v)| {
let url_str = format!("asset:///{k}");
let specifier = resolve_url(&url_str).unwrap();
let asset = Arc::new(AssetDocument::new(specifier.clone(), v.get()));
(specifier, asset)
})
.collect(),
),
});
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AssetOrDocument { pub enum AssetOrDocument {
Document(Arc<Document>), Document(Arc<Document>),
Asset(AssetDocument), Asset(Arc<AssetDocument>),
} }
impl AssetOrDocument { impl AssetOrDocument {
@ -218,10 +296,12 @@ impl AssetOrDocument {
} }
} }
pub fn text(&self) -> Arc<str> { pub fn text(&self) -> MaybeStaticSource {
match self { match self {
AssetOrDocument::Asset(a) => a.text(), AssetOrDocument::Asset(a) => a.text(),
AssetOrDocument::Document(d) => d.text.clone(), AssetOrDocument::Document(d) => {
MaybeStaticSource::Computed(d.text.clone())
}
} }
} }
@ -271,6 +351,16 @@ impl AssetOrDocument {
AssetOrDocument::Document(d) => d.resolution_mode(), AssetOrDocument::Document(d) => d.resolution_mode(),
} }
} }
pub fn cache_navigation_tree(
&self,
navigation_tree: Arc<tsc::NavigationTree>,
) {
match self {
AssetOrDocument::Asset(a) => a.cache_navigation_tree(navigation_tree),
AssetOrDocument::Document(d) => d.cache_navigation_tree(navigation_tree),
}
}
} }
type ModuleResult = Result<deno_graph::JsModule, deno_graph::ModuleGraphError>; type ModuleResult = Result<deno_graph::JsModule, deno_graph::ModuleGraphError>;

View file

@ -75,6 +75,7 @@ use super::documents::Document;
use super::documents::Documents; use super::documents::Documents;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::documents::LanguageId; use super::documents::LanguageId;
use super::documents::ASSET_DOCUMENTS;
use super::jsr::CliJsrSearchApi; use super::jsr::CliJsrSearchApi;
use super::logging::lsp_log; use super::logging::lsp_log;
use super::logging::lsp_warn; use super::logging::lsp_warn;
@ -89,8 +90,6 @@ use super::resolver::LspResolver;
use super::testing; use super::testing;
use super::text; use super::text;
use super::tsc; use super::tsc;
use super::tsc::Assets;
use super::tsc::AssetsSnapshot;
use super::tsc::ChangeKind; use super::tsc::ChangeKind;
use super::tsc::GetCompletionDetailsArgs; use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer; use super::tsc::TsServer;
@ -145,7 +144,6 @@ pub struct LanguageServer {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct StateSnapshot { pub struct StateSnapshot {
pub project_version: usize, pub project_version: usize,
pub assets: AssetsSnapshot,
pub config: Arc<Config>, pub config: Arc<Config>,
pub documents: Arc<Documents>, pub documents: Arc<Documents>,
pub resolver: Arc<LspResolver>, pub resolver: Arc<LspResolver>,
@ -191,9 +189,6 @@ impl LanguageServerTaskQueue {
#[derive(Debug)] #[derive(Debug)]
pub struct Inner { pub struct Inner {
/// Cached versions of "fixed" assets that can either be inlined in Rust or
/// are part of the TypeScript snapshot and have to be fetched out.
assets: Assets,
cache: LspCache, cache: LspCache,
/// The LSP client that this LSP server is connected to. /// The LSP client that this LSP server is connected to.
pub client: Client, pub client: Client,
@ -498,13 +493,11 @@ impl Inner {
ts_server.clone(), ts_server.clone(),
diagnostics_state.clone(), diagnostics_state.clone(),
); );
let assets = Assets::new();
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| { let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory") panic!("Could not resolve current working directory")
}); });
Self { Self {
assets,
cache, cache,
client, client,
config, config,
@ -553,7 +546,7 @@ impl Inner {
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<AssetOrDocument> { ) -> Option<AssetOrDocument> {
if specifier.scheme() == "asset" { if specifier.scheme() == "asset" {
self.assets.get(specifier).map(AssetOrDocument::Asset) ASSET_DOCUMENTS.get(specifier).map(AssetOrDocument::Asset)
} else { } else {
self.documents.get(specifier).map(AssetOrDocument::Document) self.documents.get(specifier).map(AssetOrDocument::Document)
} }
@ -584,14 +577,7 @@ impl Inner {
) )
.await?; .await?;
let navigation_tree = Arc::new(navigation_tree); let navigation_tree = Arc::new(navigation_tree);
match asset_or_doc { asset_or_doc.cache_navigation_tree(navigation_tree.clone());
AssetOrDocument::Asset(_) => self
.assets
.cache_navigation_tree(specifier, navigation_tree.clone())?,
AssetOrDocument::Document(doc) => {
doc.cache_navigation_tree(navigation_tree.clone());
}
}
navigation_tree navigation_tree
}; };
self.performance.measure(mark); self.performance.measure(mark);
@ -627,7 +613,6 @@ impl Inner {
pub fn snapshot(&self) -> Arc<StateSnapshot> { pub fn snapshot(&self) -> Arc<StateSnapshot> {
Arc::new(StateSnapshot { Arc::new(StateSnapshot {
project_version: self.project_version, project_version: self.project_version,
assets: self.assets.snapshot(),
config: Arc::new(self.config.clone()), config: Arc::new(self.config.clone()),
documents: Arc::new(self.documents.clone()), documents: Arc::new(self.documents.clone()),
resolver: self.resolver.snapshot(), resolver: self.resolver.snapshot(),
@ -2805,7 +2790,7 @@ impl Inner {
} }
Ok(span.to_folding_range( Ok(span.to_folding_range(
asset_or_doc.line_index(), asset_or_doc.line_index(),
asset_or_doc.text().as_bytes(), asset_or_doc.text().as_ref().as_bytes(),
self.config.line_folding_only_capable(), self.config.line_folding_only_capable(),
)) ))
}) })

View file

@ -76,6 +76,7 @@ use super::config::LspTsConfig;
use super::documents::AssetOrDocument; use super::documents::AssetOrDocument;
use super::documents::Document; use super::documents::Document;
use super::documents::DocumentsFilter; use super::documents::DocumentsFilter;
use super::documents::ASSET_DOCUMENTS;
use super::language_server; use super::language_server;
use super::language_server::StateSnapshot; use super::language_server::StateSnapshot;
use super::logging::lsp_log; use super::logging::lsp_log;
@ -96,7 +97,7 @@ use super::urls::INVALID_URI;
use crate::args::jsr_url; use crate::args::jsr_url;
use crate::args::FmtOptionsConfig; use crate::args::FmtOptionsConfig;
use crate::lsp::logging::lsp_warn; use crate::lsp::logging::lsp_warn;
use crate::tsc; use crate::tsc::MaybeStaticSource;
use crate::tsc::ResolveArgs; use crate::tsc::ResolveArgs;
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER; use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
use crate::util::path::relative_specifier; use crate::util::path::relative_specifier;
@ -1398,124 +1399,6 @@ impl TsServer {
} }
} }
/// An lsp representation of an asset in memory, that has either been retrieved
/// from static assets built into Rust, or static assets built into tsc.
#[derive(Debug, Clone)]
pub struct AssetDocument {
specifier: ModuleSpecifier,
text: Arc<str>,
line_index: Arc<LineIndex>,
maybe_navigation_tree: Option<Arc<NavigationTree>>,
}
impl AssetDocument {
pub fn new(specifier: ModuleSpecifier, text: impl AsRef<str>) -> Self {
let text = text.as_ref();
Self {
specifier,
text: text.into(),
line_index: Arc::new(LineIndex::new(text)),
maybe_navigation_tree: None,
}
}
pub fn specifier(&self) -> &ModuleSpecifier {
&self.specifier
}
pub fn with_navigation_tree(&self, tree: Arc<NavigationTree>) -> Self {
Self {
maybe_navigation_tree: Some(tree),
..self.clone()
}
}
pub fn text(&self) -> Arc<str> {
self.text.clone()
}
pub fn line_index(&self) -> Arc<LineIndex> {
self.line_index.clone()
}
pub fn maybe_navigation_tree(&self) -> Option<Arc<NavigationTree>> {
self.maybe_navigation_tree.clone()
}
}
type AssetsMap = HashMap<ModuleSpecifier, AssetDocument>;
fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
let assets = tsc::LAZILY_LOADED_STATIC_ASSETS
.iter()
.map(|(k, v)| {
let url_str = format!("asset:///{k}");
let specifier = resolve_url(&url_str).unwrap();
let asset = AssetDocument::new(specifier.clone(), v.get());
(specifier, asset)
})
.collect::<AssetsMap>();
Arc::new(Mutex::new(assets))
}
/// Snapshot of Assets.
#[derive(Debug, Clone)]
pub struct AssetsSnapshot(Arc<Mutex<AssetsMap>>);
impl Default for AssetsSnapshot {
fn default() -> Self {
Self(new_assets_map())
}
}
impl AssetsSnapshot {
pub fn contains_key(&self, k: &ModuleSpecifier) -> bool {
self.0.lock().contains_key(k)
}
pub fn get(&self, k: &ModuleSpecifier) -> Option<AssetDocument> {
self.0.lock().get(k).cloned()
}
}
/// Assets are never updated and so we can safely use this struct across
/// multiple threads without needing to worry about race conditions.
#[derive(Debug, Clone)]
pub struct Assets {
assets: Arc<Mutex<AssetsMap>>,
}
impl Assets {
pub fn new() -> Self {
Self {
assets: new_assets_map(),
}
}
pub fn snapshot(&self) -> AssetsSnapshot {
// it's ok to not make a complete copy for snapshotting purposes
// because assets are static
AssetsSnapshot(self.assets.clone())
}
pub fn get(&self, specifier: &ModuleSpecifier) -> Option<AssetDocument> {
self.assets.lock().get(specifier).cloned()
}
pub fn cache_navigation_tree(
&self,
specifier: &ModuleSpecifier,
navigation_tree: Arc<NavigationTree>,
) -> Result<(), AnyError> {
let mut assets = self.assets.lock();
let doc = assets
.get_mut(specifier)
.ok_or_else(|| anyhow!("Missing asset."))?;
*doc = doc.with_navigation_tree(navigation_tree);
Ok(())
}
}
fn get_tag_body_text( fn get_tag_body_text(
tag: &JsDocTagInfo, tag: &JsDocTagInfo,
language_server: &language_server::Inner, language_server: &language_server::Inner,
@ -4553,9 +4436,8 @@ impl State {
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Option<AssetOrDocument> { ) -> Option<AssetOrDocument> {
let snapshot = &self.state_snapshot;
if specifier.scheme() == "asset" { if specifier.scheme() == "asset" {
snapshot.assets.get(specifier).map(AssetOrDocument::Asset) ASSET_DOCUMENTS.get(specifier).map(AssetOrDocument::Asset)
} else { } else {
let document = self.get_document(specifier); let document = self.get_document(specifier);
document.map(AssetOrDocument::Document) document.map(AssetOrDocument::Document)
@ -4564,7 +4446,7 @@ impl State {
fn script_version(&self, specifier: &ModuleSpecifier) -> Option<String> { fn script_version(&self, specifier: &ModuleSpecifier) -> Option<String> {
if specifier.scheme() == "asset" { if specifier.scheme() == "asset" {
if self.state_snapshot.assets.contains_key(specifier) { if ASSET_DOCUMENTS.contains_key(specifier) {
Some("1".to_string()) Some("1".to_string())
} else { } else {
None None
@ -4622,7 +4504,7 @@ enum LoadError {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct LoadResponse { struct LoadResponse {
data: Arc<str>, data: MaybeStaticSource,
script_kind: i32, script_kind: i32,
version: Option<String>, version: Option<String>,
is_cjs: bool, is_cjs: bool,
@ -5876,7 +5758,6 @@ mod tests {
let snapshot = Arc::new(StateSnapshot { let snapshot = Arc::new(StateSnapshot {
project_version: 0, project_version: 0,
documents: Arc::new(documents), documents: Arc::new(documents),
assets: Default::default(),
config: Arc::new(config), config: Arc::new(config),
resolver, resolver,
}); });

View file

@ -169,7 +169,8 @@ pub enum StaticAssetSource {
} }
/// Like a `Cow` but the owned form is an `Arc<str>` instead of `String` /// Like a `Cow` but the owned form is an `Arc<str>` instead of `String`
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum MaybeStaticSource { pub enum MaybeStaticSource {
Computed(Arc<str>), Computed(Arc<str>),
Static(&'static str), Static(&'static str),