Files
zesdex/apps/infrastructure/src/bgbash/job.rs
T

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))
})
}
}