77 lines
2.0 KiB
Rust
77 lines
2.0 KiB
Rust
//! Background bash job — spawns a `bash -c` subprocess and tracks its life.
|
|
|
|
use std::process::{Child, Command, Stdio};
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use tracing::error;
|
|
|
|
/// A handle to a spawned background bash job.
|
|
pub struct BashJob {
|
|
pub id: String,
|
|
pub command: String,
|
|
pub process: Mutex<Option<Child>>,
|
|
pub cancelled: AtomicBool,
|
|
}
|
|
|
|
/// Spawn a background bash job and return a handle.
|
|
///
|
|
/// The job runs until completion or until `cancel()` is called.
|
|
pub fn spawn_bash_job(cmd: String) -> Arc<BashJob> {
|
|
let child = Command::new("bash")
|
|
.arg("-c")
|
|
.arg(&cmd)
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.ok();
|
|
|
|
let job = Arc::new(BashJob {
|
|
id: uuid::Uuid::new_v4().to_string(),
|
|
command: cmd,
|
|
process: Mutex::new(child),
|
|
cancelled: AtomicBool::new(false),
|
|
});
|
|
|
|
// Spawn a monitor thread (in production this would use an async task)
|
|
let job_clone = Arc::clone(&job);
|
|
std::thread::spawn(move || {
|
|
let mut guard = match job_clone.process.lock() {
|
|
Ok(g) => g,
|
|
Err(poisoned) => {
|
|
error!("bgbash job mutex poisoned, recovering");
|
|
poisoned.into_inner()
|
|
}
|
|
};
|
|
if let Some(ref mut child) = *guard {
|
|
let _ = child.wait();
|
|
}
|
|
});
|
|
|
|
job
|
|
}
|
|
|
|
impl BashJob {
|
|
pub fn cancel(&self) {
|
|
self.cancelled.store(true, Ordering::SeqCst);
|
|
if let Ok(mut guard) = self.process.lock() {
|
|
if let Some(ref mut child) = *guard {
|
|
let _ = child.kill();
|
|
let _ = child.wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn is_running(&self) -> bool {
|
|
if self.cancelled.load(Ordering::SeqCst) {
|
|
return false;
|
|
}
|
|
let Ok(mut guard) = self.process.lock() else {
|
|
return false;
|
|
};
|
|
guard.as_mut().map_or(false, |c| {
|
|
matches!(c.try_wait(), Ok(None))
|
|
})
|
|
}
|
|
}
|