Repository: denoland/deno
Pull request: perf(lsp): don't clone asset text


Changed files:
filename: cli/lsp/diagnostics.rs
patch: @@ -1999,7 +1999,6 @@ mod tests {
       StateSnapshot {
         project_version: 0,
         documents: Arc::new(documents),
-        assets: Default::default(),
         config: Arc::new(config),
         resolver,
       },

filename: cli/lsp/documents.rs
patch: @@ -23,6 +23,8 @@ use deno_core::futures::future;
 use deno_core::futures::future::Shared;
 use deno_core::futures::FutureExt;
 use deno_core::parking_lot::Mutex;
+use deno_core::parking_lot::RwLock;
+use deno_core::resolve_url;
 use deno_core::ModuleSpecifier;
 use deno_error::JsErrorBox;
 use deno_graph::Resolution;
@@ -36,6 +38,7 @@ use indexmap::IndexSet;
 use node_resolver::cache::NodeResolutionThreadLocalCache;
 use node_resolver::NodeResolutionKind;
 use node_resolver::ResolutionMode;
+use once_cell::sync::Lazy;
 use tower_lsp::lsp_types as lsp;
 
 use super::cache::calculate_fs_version;
@@ -48,8 +51,8 @@ use super::testing::TestCollector;
 use super::testing::TestModule;
 use super::text::LineIndex;
 use super::tsc;
-use super::tsc::AssetDocument;
 use crate::graph_util::CliJsrUrlProvider;
+use crate::tsc::MaybeStaticSource;
 
 pub const DOCUMENT_SCHEMES: [&str; 5] =
   ["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)]
 pub enum AssetOrDocument {
   Document(Arc<Document>),
-  Asset(AssetDocument),
+  Asset(Arc<AssetDocument>),
 }
 
 impl AssetOrDocument {
@@ -218,10 +296,12 @@ impl AssetOrDocument {
     }
   }
 
-  pub fn text(&self) -> Arc<str> {
+  pub fn text(&self) -> MaybeStaticSource {
     match self {
       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(),
     }
   }
+
+  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>;

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

filename: cli/lsp/tsc.rs
patch: @@ -76,6 +76,7 @@ use super::config::LspTsConfig;
 use super::documents::AssetOrDocument;
 use super::documents::Document;
 use super::documents::DocumentsFilter;
+use super::documents::ASSET_DOCUMENTS;
 use super::language_server;
 use super::language_server::StateSnapshot;
 use super::logging::lsp_log;
@@ -96,7 +97,7 @@ use super::urls::INVALID_URI;
 use crate::args::jsr_url;
 use crate::args::FmtOptionsConfig;
 use crate::lsp::logging::lsp_warn;
-use crate::tsc;
+use crate::tsc::MaybeStaticSource;
 use crate::tsc::ResolveArgs;
 use crate::tsc::MISSING_DEPENDENCY_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(
   tag: &JsDocTagInfo,
   language_server: &language_server::Inner,
@@ -4553,9 +4436,8 @@ impl State {
     &self,
     specifier: &ModuleSpecifier,
   ) -> Option<AssetOrDocument> {
-    let snapshot = &self.state_snapshot;
     if specifier.scheme() == "asset" {
-      snapshot.assets.get(specifier).map(AssetOrDocument::Asset)
+      ASSET_DOCUMENTS.get(specifier).map(AssetOrDocument::Asset)
     } else {
       let document = self.get_document(specifier);
       document.map(AssetOrDocument::Document)
@@ -4564,7 +4446,7 @@ impl State {
 
   fn script_version(&self, specifier: &ModuleSpecifier) -> Option<String> {
     if specifier.scheme() == "asset" {
-      if self.state_snapshot.assets.contains_key(specifier) {
+      if ASSET_DOCUMENTS.contains_key(specifier) {
         Some("1".to_string())
       } else {
         None
@@ -4622,7 +4504,7 @@ enum LoadError {
 #[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 struct LoadResponse {
-  data: Arc<str>,
+  data: MaybeStaticSource,
   script_kind: i32,
   version: Option<String>,
   is_cjs: bool,
@@ -5876,7 +5758,6 @@ mod tests {
     let snapshot = Arc::new(StateSnapshot {
       project_version: 0,
       documents: Arc::new(documents),
-      assets: Default::default(),
       config: Arc::new(config),
       resolver,
     });

filename: cli/tsc/mod.rs
patch: @@ -169,7 +169,8 @@ pub enum StaticAssetSource {
 }
 
 /// 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 {
   Computed(Arc<str>),
   Static(&'static str),