Cache ripgrep availability check in all execution environments

The rg availability probe (rg --version / which rg) was running on every
grep() call. Cache the result in a OnceLock/OnceCell per environment so
the probe runs at most once. Also add grep -rn fallback to Daytona env,
matching the pattern already used by local and Docker envs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-27 19:47:07 -05:00
parent 915cdd2a1f
commit f211ec86d9
3 changed files with 55 additions and 28 deletions

View file

@ -60,6 +60,7 @@ pub struct DockerExecutionEnvironment {
container_id: tokio::sync::OnceCell<String>,
cached_platform: std::sync::OnceLock<String>,
cached_os_version: std::sync::OnceLock<String>,
rg_available: tokio::sync::OnceCell<bool>,
event_callback: Option<ExecEnvEventCallback>,
}
@ -77,6 +78,7 @@ impl DockerExecutionEnvironment {
container_id: tokio::sync::OnceCell::new(),
cached_platform: std::sync::OnceLock::new(),
cached_os_version: std::sync::OnceLock::new(),
rg_available: tokio::sync::OnceCell::const_new(),
event_callback: None,
})
}
@ -581,16 +583,15 @@ impl ExecutionEnvironment for DockerExecutionEnvironment {
) -> Result<Vec<String>, String> {
let container_path = self.resolve_container_path(path);
// Detect ripgrep availability
let (_, _, rg_check) = self
.docker_exec(
// Detect ripgrep availability (cached)
let use_rg = *self.rg_available.get_or_init(|| async {
let result = self.docker_exec(
vec!["which".to_string(), "rg".to_string()],
None,
None,
)
.await?;
let use_rg = rg_check == 0;
).await;
matches!(result, Ok((_, _, 0)))
}).await;
let command = if use_rg {
let mut args = vec!["rg".to_string(), "-n".to_string()];

View file

@ -9,12 +9,13 @@ use tokio_util::sync::CancellationToken;
pub struct LocalExecutionEnvironment {
working_directory: PathBuf,
event_callback: Option<ExecEnvEventCallback>,
rg_available: std::sync::OnceLock<bool>,
}
impl LocalExecutionEnvironment {
#[must_use]
pub fn new(working_directory: PathBuf) -> Self {
Self { working_directory, event_callback: None }
Self { working_directory, event_callback: None, rg_available: std::sync::OnceLock::new() }
}
pub fn set_event_callback(&mut self, cb: ExecEnvEventCallback) {
@ -230,13 +231,15 @@ impl ExecutionEnvironment for LocalExecutionEnvironment {
let full_path = self.resolve_path(path);
// Try rg (ripgrep) first, fall back to grep
let use_rg = std::process::Command::new("rg")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
let use_rg = *self.rg_available.get_or_init(|| {
std::process::Command::new("rg")
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
});
let output = if use_rg {
let mut args = vec!["-n".to_string()];

View file

@ -42,6 +42,7 @@ pub struct DaytonaExecutionEnvironment {
config: DaytonaConfig,
client: daytona_sdk::Client,
sandbox: tokio::sync::OnceCell<daytona_sdk::Sandbox>,
rg_available: tokio::sync::OnceCell<bool>,
event_callback: Option<ExecEnvEventCallback>,
}
@ -52,6 +53,7 @@ impl DaytonaExecutionEnvironment {
config,
client,
sandbox: tokio::sync::OnceCell::new(),
rg_available: tokio::sync::OnceCell::const_new(),
event_callback: None,
}
}
@ -584,23 +586,44 @@ impl ExecutionEnvironment for DaytonaExecutionEnvironment {
) -> Result<Vec<String>, String> {
let resolved = self.resolve_path(path);
// Build rg command (same approach as Docker env)
let mut cmd = "rg --line-number --no-heading".to_string();
if options.case_insensitive {
cmd.push_str(" -i");
}
if let Some(ref glob_filter) = options.glob_filter {
cmd.push_str(&format!(" --glob '{glob_filter}'"));
}
if let Some(max) = options.max_results {
cmd.push_str(&format!(" --max-count {max}"));
}
cmd.push_str(&format!(" -- '{}' '{}'", pattern.replace('\'', "'\\''"), resolved));
// Detect ripgrep availability (cached)
let use_rg = *self.rg_available.get_or_init(|| async {
let result = self.exec_command("rg --version", 10_000, None, None, None).await;
matches!(result, Ok(r) if r.exit_code == 0)
}).await;
let cmd = if use_rg {
let mut cmd = "rg --line-number --no-heading".to_string();
if options.case_insensitive {
cmd.push_str(" -i");
}
if let Some(ref glob_filter) = options.glob_filter {
cmd.push_str(&format!(" --glob '{glob_filter}'"));
}
if let Some(max) = options.max_results {
cmd.push_str(&format!(" --max-count {max}"));
}
cmd.push_str(&format!(" -- '{}' '{}'", pattern.replace('\'', "'\\''"), resolved));
cmd
} else {
let mut cmd = "grep -rn".to_string();
if options.case_insensitive {
cmd.push_str(" -i");
}
if let Some(ref glob_filter) = options.glob_filter {
cmd.push_str(&format!(" --include '{glob_filter}'"));
}
if let Some(max) = options.max_results {
cmd.push_str(&format!(" -m {max}"));
}
cmd.push_str(&format!(" -- '{}' '{}'", pattern.replace('\'', "'\\''"), resolved));
cmd
};
let result = self.exec_command(&cmd, 30_000, None, None, None).await?;
if result.exit_code == 1 {
// rg exits 1 for no matches
// Both rg and grep exit 1 for no matches
return Ok(Vec::new());
}
if result.exit_code != 0 {