2026-07-20 09:04:57 +07:00
|
|
|
//! Background bash control — list, cancel, and inspect background processes.
|
|
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
2026-07-20 12:26:08 +07:00
|
|
|
use std::sync::{Arc, Mutex, OnceLock};
|
2026-07-20 09:04:57 +07:00
|
|
|
|
2026-07-20 10:55:09 +07:00
|
|
|
use tracing::error;
|
|
|
|
|
|
2026-07-20 09:04:57 +07:00
|
|
|
use super::job::BashJob;
|
|
|
|
|
|
2026-07-20 12:26:08 +07:00
|
|
|
/// Global accessor for the shared BashControl singleton.
|
|
|
|
|
///
|
|
|
|
|
/// Used by the Bash tool (to register jobs) and BashKill (to look them up).
|
|
|
|
|
pub fn bash_control() -> &'static BashControl {
|
|
|
|
|
static BASH_CONTROL: OnceLock<BashControl> = OnceLock::new();
|
|
|
|
|
BASH_CONTROL.get_or_init(BashControl::new)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 09:04:57 +07:00
|
|
|
/// Central registry of all running background bash jobs.
|
|
|
|
|
pub struct BashControl {
|
|
|
|
|
jobs: Mutex<HashMap<String, Arc<BashJob>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BashControl {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
BashControl {
|
|
|
|
|
jobs: Mutex::new(HashMap::new()),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-20 12:02:48 +07:00
|
|
|
}
|
2026-07-20 09:04:57 +07:00
|
|
|
|
2026-07-20 12:02:48 +07:00
|
|
|
impl Default for BashControl {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl BashControl {
|
2026-07-20 09:04:57 +07:00
|
|
|
/// Register a new background job.
|
|
|
|
|
pub fn register(&self, job: Arc<BashJob>) {
|
|
|
|
|
if let Ok(mut guard) = self.jobs.lock() {
|
|
|
|
|
guard.insert(job.id.clone(), job);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Cancel a job by ID.
|
|
|
|
|
pub fn cancel(&self, id: &str) -> bool {
|
|
|
|
|
if let Ok(mut guard) = self.jobs.lock() {
|
|
|
|
|
if let Some(job) = guard.remove(id) {
|
|
|
|
|
job.cancel();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// List all active jobs.
|
|
|
|
|
pub fn list(&self) -> Vec<(String, String, bool)> {
|
2026-07-20 10:55:09 +07:00
|
|
|
let mut guard = match self.jobs.lock() {
|
|
|
|
|
Ok(g) => g,
|
|
|
|
|
Err(poisoned) => {
|
|
|
|
|
error!("bgbash jobs mutex poisoned, recovering");
|
|
|
|
|
poisoned.into_inner()
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-07-20 09:04:57 +07:00
|
|
|
guard.retain(|_, j| j.is_running());
|
|
|
|
|
guard
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(id, job)| (id.clone(), job.command.clone(), job.is_running()))
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clean up completed jobs.
|
|
|
|
|
pub fn prune(&self) {
|
|
|
|
|
if let Ok(mut guard) = self.jobs.lock() {
|
|
|
|
|
guard.retain(|_, j| j.is_running());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|