Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions src-tauri/src/benchmark/agent_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::dto::{
use super::history::{load_agent_batch_history, persist_agent_batch_status};
use super::launch::{benchmark_agent_prompt, benchmark_launch_params};
use super::paths::benchmark_agent_submission_patch_path;
use super::retention::prune_terminal_agent_batches;
use super::run::trim_logs;
use super::{
BENCHMARK_AGENT_BATCHES, BENCHMARK_AGENT_BATCH_STATUS_CANCELLED,
Expand All @@ -29,21 +30,20 @@ pub(super) async fn load_agent_batch_for_update(
return Ok(status);
}
let status = load_agent_batch_history(batch_id)?;
BENCHMARK_AGENT_BATCHES
.lock()
.await
.insert(batch_id.to_string(), status.clone());
let mut batches = BENCHMARK_AGENT_BATCHES.lock().await;
batches.insert(batch_id.to_string(), status.clone());
prune_terminal_agent_batches(&mut batches);
Ok(status)
}

pub(super) async fn persist_updated_agent_batch(
mut batch: BenchmarkAgentBatchStatus,
) -> Result<BenchmarkAgentBatchStatus, String> {
refresh_agent_batch_counts(&mut batch);
BENCHMARK_AGENT_BATCHES
.lock()
.await
.insert(batch.batch_id.clone(), batch.clone());
let mut batches = BENCHMARK_AGENT_BATCHES.lock().await;
batches.insert(batch.batch_id.clone(), batch.clone());
prune_terminal_agent_batches(&mut batches);
drop(batches);
persist_agent_batch_status(&batch)?;
Ok(batch)
}
Expand Down Expand Up @@ -71,10 +71,10 @@ pub(super) async fn refresh_agent_batch_evaluations(
}
drop(runs);
if changed {
BENCHMARK_AGENT_BATCHES
.lock()
.await
.insert(batch.batch_id.clone(), batch.clone());
let mut batches = BENCHMARK_AGENT_BATCHES.lock().await;
batches.insert(batch.batch_id.clone(), batch.clone());
prune_terminal_agent_batches(&mut batches);
drop(batches);
persist_agent_batch_status(&batch)?;
}
Ok(batch)
Expand Down Expand Up @@ -237,7 +237,9 @@ where
update(item);
}
refresh_agent_batch_counts(batch);
batch.clone()
let status = batch.clone();
prune_terminal_agent_batches(&mut batches);
status
};
if let Err(error) = persist_agent_batch_status(&status) {
tracing::warn!(
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/src/benchmark/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use super::launch::{
use super::paths::benchmark_agent_submission_patch_path;
use super::preflight::{run_swe_bench_preflight, run_terminal_bench_preflight};
use super::process::terminate_process;
use super::retention::{prune_terminal_agent_batches, prune_terminal_runs};
use super::run::trim_logs;
use super::swe_bench::{
build_swe_bench_run_plan, run_swe_bench_patch_only_worktree, run_swe_bench_process,
Expand Down Expand Up @@ -215,7 +216,9 @@ pub async fn benchmark_cancel_run(
.push("Cancel requested for evaluator process.".to_string());
trim_logs(&mut status.logs);
}
status.process_id
let process_id = status.process_id;
prune_terminal_runs(&mut runs);
process_id
};

if let Some(pid) = process_id {
Expand Down Expand Up @@ -623,6 +626,7 @@ pub async fn benchmark_cancel_agent_batch(
batch.finished_at = Some(now);
refresh_agent_batch_counts(batch);
let status = batch.clone();
prune_terminal_agent_batches(&mut batches);
drop(batches);
persist_agent_batch_status(&status)?;
Ok(status)
Expand Down
29 changes: 29 additions & 0 deletions src-tauri/src/benchmark/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod launch;
mod paths;
mod preflight;
mod process;
mod retention;
mod run;
mod swe_bench;

Expand Down Expand Up @@ -74,3 +75,31 @@ static BENCHMARK_RUNS: LazyLock<Arc<Mutex<HashMap<String, BenchmarkRunStatus>>>>
LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
static BENCHMARK_AGENT_BATCHES: LazyLock<Arc<Mutex<HashMap<String, BenchmarkAgentBatchStatus>>>> =
LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));

/// Best-effort termination of evaluator processes that are still running
/// when the app exits. Called from the `ExitRequested` handler alongside the
/// other subprocess cleanup; without it the spawned Python/Docker evaluators
/// would outlive the app as orphans.
pub fn terminate_running_evaluators_sync() {
let Ok(runs) = BENCHMARK_RUNS.try_lock() else {
tracing::warn!(
"[benchmark] runs registry locked during shutdown; skipping evaluator cleanup"
);
return;
};
let process_ids: Vec<u32> = runs
.values()
.filter(|run| run.status == BENCHMARK_RUN_STATUS_RUNNING)
.filter_map(|run| run.process_id)
.collect();
drop(runs);
for process_id in process_ids {
if let Err(error) = process::terminate_process_sync(process_id) {
tracing::warn!(
process_id,
error = %error,
"[benchmark] failed to terminate evaluator during shutdown"
);
}
}
}
36 changes: 36 additions & 0 deletions src-tauri/src/benchmark/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,42 @@ pub(super) async fn run_python_import(
})
}

/// Synchronous variant of [`terminate_process`] for shutdown paths that run
/// outside the async runtime (the Tauri `ExitRequested` handler).
pub(super) fn terminate_process_sync(process_id: u32) -> Result<(), String> {
#[cfg(unix)]
let output = std::process::Command::new("kill")
.arg("-TERM")
.arg(process_id.to_string())
.output();

#[cfg(windows)]
let output = {
use std::os::windows::process::CommandExt;
let mut cmd = std::process::Command::new("taskkill");
cmd.arg("/PID")
.arg(process_id.to_string())
.arg("/T")
.arg("/F");
// Suppress the console window on Windows.
cmd.creation_flags(app_platform::CREATE_NO_WINDOW);
cmd.output()
};

let output =
output.map_err(|error| format!("Failed to terminate process {process_id}: {error}"))?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
Err(if stderr.is_empty() {
format!("Process termination exited with {}", output.status)
} else {
stderr
})
}
}

pub(super) async fn terminate_process(process_id: u32) -> Result<(), String> {
#[cfg(unix)]
let output = Command::new("kill")
Expand Down
Loading
Loading