Fix local filetree page tree open performance
This commit is contained in:
@@ -31,9 +31,53 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[cfg(test)]
|
||||
static LOCAL_PAGE_TREE_SNAPSHOT_TEST_LOADS: std::sync::atomic::AtomicU64 =
|
||||
std::sync::atomic::AtomicU64::new(0);
|
||||
#[cfg(test)]
|
||||
static LOCAL_PAGE_TREE_SNAPSHOT_SCAN_TEST_LOADS: std::sync::atomic::AtomicU64 =
|
||||
std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LocalPageTreeSnapshotCacheEntry {
|
||||
watch_revision: LocalFolderWatchRevision,
|
||||
snapshot: ProjectionSnapshot,
|
||||
}
|
||||
|
||||
static LOCAL_PAGE_TREE_SNAPSHOT_CACHE: OnceLock<
|
||||
Mutex<BTreeMap<String, LocalPageTreeSnapshotCacheEntry>>,
|
||||
> = OnceLock::new();
|
||||
|
||||
fn local_page_tree_snapshot_cache(
|
||||
) -> &'static Mutex<BTreeMap<String, LocalPageTreeSnapshotCacheEntry>> {
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn reset_local_page_tree_snapshot_test_loads() {
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_TEST_LOADS.store(0, std::sync::atomic::Ordering::SeqCst);
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_SCAN_TEST_LOADS.store(0, std::sync::atomic::Ordering::SeqCst);
|
||||
if let Some(cache) = LOCAL_PAGE_TREE_SNAPSHOT_CACHE.get() {
|
||||
if let Ok(mut cache) = cache.lock() {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn local_page_tree_snapshot_test_loads() -> u64 {
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_TEST_LOADS.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn local_page_tree_snapshot_scan_test_loads() -> u64 {
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_SCAN_TEST_LOADS.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LocalFolderEntry {
|
||||
path: PathBuf,
|
||||
@@ -2724,6 +2768,9 @@ fn load_local_folder_file_tree_scope_snapshot(
|
||||
pub fn load_local_folder_page_tree_snapshot(
|
||||
root_uri: &str,
|
||||
) -> Result<ProjectionSnapshot, WebError> {
|
||||
#[cfg(test)]
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_TEST_LOADS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
let root_path = parse_file_root_uri(root_uri)?;
|
||||
let canonical_root = root_path.canonicalize().map_err(|error| {
|
||||
WebError::bad_request_code(
|
||||
@@ -2740,8 +2787,21 @@ pub fn load_local_folder_page_tree_snapshot(
|
||||
|
||||
let workspace_id = local_workspace_id(&canonical_root);
|
||||
let root_source_uri = file_uri_for_path(&canonical_root);
|
||||
let watch_revision = local_folder_watch_revision_for_root(&canonical_root, &root_source_uri)?;
|
||||
if let Ok(cache) = local_page_tree_snapshot_cache().lock() {
|
||||
if let Some(entry) = cache.get(&root_source_uri) {
|
||||
if entry.watch_revision.revision == watch_revision.revision
|
||||
&& entry.watch_revision.entry_count == watch_revision.entry_count
|
||||
&& entry.watch_revision.latest_modified_ms == watch_revision.latest_modified_ms
|
||||
{
|
||||
return Ok(entry.snapshot.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let metadata = load_local_folder_metadata(&canonical_root)?;
|
||||
let mut rows = Vec::new();
|
||||
#[cfg(test)]
|
||||
LOCAL_PAGE_TREE_SNAPSHOT_SCAN_TEST_LOADS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
scan_markdown_page_tree(
|
||||
&canonical_root,
|
||||
&canonical_root,
|
||||
@@ -2773,8 +2833,7 @@ pub fn load_local_folder_page_tree_snapshot(
|
||||
.iter()
|
||||
.map(local_folder_row_to_projection_item)
|
||||
.collect::<Vec<_>>();
|
||||
let watch_revision = local_folder_watch_revision_for_root(&canonical_root, &root_source_uri)?;
|
||||
Ok(ProjectionSnapshot {
|
||||
let snapshot = ProjectionSnapshot {
|
||||
dataset: json!({
|
||||
"workspace": {
|
||||
"id": local_workspace_id(&canonical_root),
|
||||
@@ -2803,7 +2862,20 @@ pub fn load_local_folder_page_tree_snapshot(
|
||||
"watchRevision": watch_revision,
|
||||
"items": items,
|
||||
}),
|
||||
})
|
||||
};
|
||||
if let Ok(mut cache) = local_page_tree_snapshot_cache().lock() {
|
||||
if cache.len() > 64 {
|
||||
cache.clear();
|
||||
}
|
||||
cache.insert(
|
||||
root_source_uri,
|
||||
LocalPageTreeSnapshotCacheEntry {
|
||||
watch_revision,
|
||||
snapshot: snapshot.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(snapshot)
|
||||
}
|
||||
|
||||
pub fn local_folder_watch_revision(root_uri: &str) -> Result<LocalFolderWatchRevision, WebError> {
|
||||
@@ -9070,6 +9142,27 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_folder_page_tree_snapshot_reuses_cache_until_watch_revision_changes() {
|
||||
let root = temp_root("mnote-page-tree-snapshot-cache");
|
||||
init_workspace(&root);
|
||||
std::fs::write(root.join("page.md"), "# Page\n").expect("write md");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
super::reset_local_page_tree_snapshot_test_loads();
|
||||
|
||||
let first = load_local_folder_page_tree_snapshot(&root_uri).expect("first snapshot");
|
||||
let second = load_local_folder_page_tree_snapshot(&root_uri).expect("second snapshot");
|
||||
assert_eq!(first.projection, second.projection);
|
||||
assert_eq!(super::local_page_tree_snapshot_scan_test_loads(), 1);
|
||||
|
||||
std::fs::write(root.join("next.md"), "# Next\n").expect("write next");
|
||||
let third = load_local_folder_page_tree_snapshot(&root_uri).expect("third snapshot");
|
||||
assert_ne!(second.projection, third.projection);
|
||||
assert_eq!(super::local_page_tree_snapshot_scan_test_loads(), 2);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_markdown_identity_uses_path_even_when_frontmatter_has_mnote_id() {
|
||||
let root = temp_root("mnote-local-frontmatter-path-id");
|
||||
|
||||
Reference in New Issue
Block a user