use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
#[derive(Debug, Default)]
pub struct ResourceLedger {
pub chrome_launched: bool,
pub chrome_pid: Option<u32>,
pub profile_dir: Option<PathBuf>,
pub side_channels: Vec<PathBuf>,
pub windows_job_handle: usize,
pub started_at: Option<std::time::SystemTime>,
}
#[derive(Clone)]
pub struct Lifecycle {
pub cancel: CancellationToken,
pub finalize_done: Arc<AtomicBool>,
pub ledger: Arc<std::sync::Mutex<ResourceLedger>>,
}
impl Lifecycle {
pub fn new() -> Self {
let lc = Self {
cancel: CancellationToken::new(),
finalize_done: Arc::new(AtomicBool::new(false)),
ledger: Arc::new(std::sync::Mutex::new(ResourceLedger::default())),
};
if let Ok(mut ledger) = lc.ledger.lock() {
ledger.started_at = Some(std::time::SystemTime::now());
}
lc
}
pub fn finalize(&self) {
if self
.finalize_done
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return;
}
self.cancel.cancel();
if let Ok(mut ledger) = self.ledger.lock() {
if ledger.chrome_launched {
if let Some(pid) = ledger.chrome_pid.take() {
#[cfg(unix)]
unsafe {
let _ = libc::kill(pid as i32, libc::SIGTERM);
let _ = libc::kill(pid as i32, libc::SIGKILL);
}
#[cfg(windows)]
{
if ledger.windows_job_handle != 0 {
crate::win_job::terminate_job(ledger.windows_job_handle);
ledger.windows_job_handle = 0;
} else {
crate::win_job::terminate_pid(pid);
}
}
}
}
ledger.chrome_launched = false;
let profile = ledger.profile_dir.take();
if let Some(ref dir) = profile {
wipe_owned_path(dir);
}
let sides = std::mem::take(&mut ledger.side_channels);
for p in sides {
wipe_owned_path(&p);
}
let not_before = ledger.started_at.unwrap_or_else(std::time::SystemTime::now);
let chrome_pid = ledger.chrome_pid; let _ = crate::residual::scavenge_owned_chromium_tmp_orphans(
profile.as_deref(),
chrome_pid,
not_before,
);
#[cfg(windows)]
if ledger.windows_job_handle != 0 {
crate::win_job::close_job(ledger.windows_job_handle);
ledger.windows_job_handle = 0;
}
}
}
}
fn wipe_owned_path(path: &std::path::Path) {
if !path.exists() {
return;
}
if path.is_dir() {
let _ = std::fs::remove_dir_all(path);
} else {
let _ = std::fs::remove_file(path);
}
}
pub fn mark_profile_dir(life: &Lifecycle, dir: Option<PathBuf>) {
if let Some(dir) = dir {
if let Ok(mut ledger) = life.ledger.lock() {
ledger.profile_dir = Some(dir);
}
}
}
pub fn mark_side_channel(life: &Lifecycle, path: PathBuf) {
if let Ok(mut ledger) = life.ledger.lock() {
if !ledger.side_channels.iter().any(|p| p == &path) {
ledger.side_channels.push(path);
}
}
}
impl Default for Lifecycle {
fn default() -> Self {
Self::new()
}
}
impl Drop for Lifecycle {
fn drop(&mut self) {
self.finalize();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finalize_is_idempotent() {
let lc = Lifecycle::new();
lc.finalize();
lc.finalize();
assert!(lc.finalize_done.load(Ordering::SeqCst));
}
}