Skip to content

Commit 4c3294c

Browse files
jdelliotfacebook-github-bot
authored andcommitted
Add caching of status computation
Summary: # Context: We are working to refactor the EdenFs CLI client library into a supported XFN consumable library to avoid code duplication and sub-optimal usage patterns. # This Diff: Watchman had some caching of frequently ran Sapling operations. This will be handly for clients that are more long-running (e.g. buck2 and meerkat in daemon mode). Here we are adding caching to status results. Reviewed By: lXXXw Differential Revision: D73058808 fbshipit-source-id: bc11821a28cb64493e865a2d12a1ea39fbd7e55f
1 parent 8f9b714 commit 4c3294c

File tree

1 file changed

+63
-39
lines changed

1 file changed

+63
-39
lines changed

eden/fs/cli_rs/sapling-client/src/status.rs

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
use std::path::Path;
99
use std::path::PathBuf;
1010
use std::process::Stdio;
11+
use std::sync::LazyLock;
12+
use std::sync::Mutex;
1113

1214
use async_process_traits::Child;
1315
use async_process_traits::Command;
1416
use async_process_traits::CommandSpawner;
1517
use async_process_traits::TokioCommandSpawner;
18+
use lru_cache::LruCache;
1619
use tokio::io::AsyncBufReadExt;
1720
use tokio::io::BufReader;
1821

@@ -23,13 +26,20 @@ use crate::utils::get_sapling_executable_path;
2326
use crate::utils::get_sapling_options;
2427
use crate::utils::process_one_status_line;
2528

29+
// NOTE: We might wish to cache Results here, but we would want to add a way to evict
30+
// Err entries from the cache based on some policy - e.g. a TTL in seconds.
31+
// For now, we just cache Ok entries.
32+
const STATUS_LRU_CACHE_SIZE: usize = 32;
33+
static STATUS_LRU_CACHE: LazyLock<Mutex<LruCache<GetStatusParams, SaplingGetStatusResult>>> =
34+
LazyLock::new(|| Mutex::new(LruCache::new(STATUS_LRU_CACHE_SIZE)));
35+
2636
#[derive(Clone, Debug, PartialEq)]
2737
pub enum SaplingGetStatusResult {
2838
Normal(Vec<(SaplingStatus, String)>),
2939
TooManyChanges,
3040
}
3141

32-
#[derive(Clone, Debug, Hash, PartialEq)]
42+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
3343
struct GetStatusParams {
3444
first: String,
3545
second: Option<String>,
@@ -130,52 +140,66 @@ where
130140
.collect::<Vec<String>>()
131141
});
132142

133-
let mut args = vec!["status", "-mardu", "--rev", &params.first];
134-
let second: String;
135-
if let Some(second_) = params.second {
136-
second = second_;
137-
args.push("--rev");
138-
args.push(&second);
143+
{
144+
let mut lru_cache = STATUS_LRU_CACHE.lock().unwrap();
145+
let entry = lru_cache.get_mut(&params).cloned();
146+
if let Some(entry) = entry {
147+
return Ok(entry);
148+
}
139149
}
140150

141-
let root_path_arg: String;
142-
if let Some(root) = params.root {
143-
root_path_arg = format!("path:{}", root.display());
144-
args.push(&root_path_arg);
145-
};
151+
let result = {
152+
let mut args = vec!["status", "-mardu", "--rev", &params.first];
153+
let second: String;
154+
if let Some(second_) = &params.second {
155+
second = second_.to_string();
156+
args.push("--rev");
157+
args.push(&second);
158+
}
159+
160+
let root_path_arg: String;
161+
if let Some(root) = &params.root {
162+
root_path_arg = format!("path:{}", root.display());
163+
args.push(&root_path_arg);
164+
};
146165

147-
let mut command = Spawner::Command::new(get_sapling_executable_path());
148-
command
149-
.envs(get_sapling_options())
150-
.args(args)
151-
.stdout(Stdio::piped());
152-
let mut child = spawner.spawn(&mut command)?;
153-
let stdout = child.stdout().take().ok_or_else(|| {
154-
SaplingError::Other("Failed to read stdout when invoking 'sl status'.".to_string())
155-
})?;
156-
let reader = BufReader::new(stdout);
166+
let mut command = Spawner::Command::new(get_sapling_executable_path());
167+
command
168+
.envs(get_sapling_options())
169+
.args(args)
170+
.stdout(Stdio::piped());
171+
let mut child = spawner.spawn(&mut command)?;
172+
let stdout = child.stdout().take().ok_or_else(|| {
173+
SaplingError::Other("Failed to read stdout when invoking 'sl status'.".to_string())
174+
})?;
175+
let reader = BufReader::new(stdout);
157176

158-
let mut status = vec![];
159-
let mut lines = reader.lines();
160-
while let Some(line) = lines.next_line().await? {
161-
if let Some(status_line) = process_one_status_line(&line)? {
162-
if is_path_included(
163-
params.case_insensitive_suffix_compares,
164-
&status_line.1,
165-
&params.included_roots,
166-
&params.included_suffixes,
167-
&params.excluded_roots,
168-
&params.excluded_suffixes,
169-
) {
170-
if status.len() >= params.limit_results {
171-
return Ok(SaplingGetStatusResult::TooManyChanges);
177+
let mut status = vec![];
178+
let mut lines = reader.lines();
179+
while let Some(line) = lines.next_line().await? {
180+
if let Some(status_line) = process_one_status_line(&line)? {
181+
if is_path_included(
182+
params.case_insensitive_suffix_compares,
183+
&status_line.1,
184+
&params.included_roots,
185+
&params.included_suffixes,
186+
&params.excluded_roots,
187+
&params.excluded_suffixes,
188+
) {
189+
if status.len() >= params.limit_results {
190+
return Ok(SaplingGetStatusResult::TooManyChanges);
191+
}
192+
status.push(status_line);
172193
}
173-
status.push(status_line);
174194
}
175195
}
176-
}
177196

178-
Ok(SaplingGetStatusResult::Normal(status))
197+
SaplingGetStatusResult::Normal(status)
198+
};
199+
200+
let mut lru_cache = STATUS_LRU_CACHE.lock().unwrap();
201+
lru_cache.insert(params, result.clone());
202+
Ok(result)
179203
}
180204

181205
fn is_path_included(

0 commit comments

Comments
 (0)