browser_automation_cli/
lifecycle.rs1use std::path::PathBuf;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::Arc;
20
21use tokio_util::sync::CancellationToken;
22
23#[derive(Debug, Default)]
25pub struct ResourceLedger {
26 pub chrome_launched: bool,
28 pub chrome_pid: Option<u32>,
30 pub profile_dir: Option<PathBuf>,
32 pub side_channels: Vec<PathBuf>,
34 pub windows_job_handle: usize,
36 pub started_at: Option<std::time::SystemTime>,
38}
39
40#[derive(Clone)]
42pub struct Lifecycle {
43 pub cancel: CancellationToken,
45 pub finalize_done: Arc<AtomicBool>,
47 pub ledger: Arc<std::sync::Mutex<ResourceLedger>>,
49}
50
51impl Lifecycle {
52 pub fn new() -> Self {
54 let lc = Self {
55 cancel: CancellationToken::new(),
56 finalize_done: Arc::new(AtomicBool::new(false)),
57 ledger: Arc::new(std::sync::Mutex::new(ResourceLedger::default())),
58 };
59 if let Ok(mut ledger) = lc.ledger.lock() {
60 ledger.started_at = Some(std::time::SystemTime::now());
61 }
62 lc
63 }
64
65 pub fn finalize(&self) {
69 if self
70 .finalize_done
71 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
72 .is_err()
73 {
74 return;
75 }
76 self.cancel.cancel();
77 if let Ok(mut ledger) = self.ledger.lock() {
78 if ledger.chrome_launched {
79 if let Some(pid) = ledger.chrome_pid.take() {
80 #[cfg(unix)]
81 unsafe {
82 let _ = libc::kill(pid as i32, libc::SIGTERM);
83 let _ = libc::kill(pid as i32, libc::SIGKILL);
84 }
85 #[cfg(windows)]
86 {
87 if ledger.windows_job_handle != 0 {
89 crate::win_job::terminate_job(ledger.windows_job_handle);
90 ledger.windows_job_handle = 0;
91 } else {
92 crate::win_job::terminate_pid(pid);
93 }
94 }
95 }
96 }
97 ledger.chrome_launched = false;
98 let profile = ledger.profile_dir.take();
99 if let Some(ref dir) = profile {
100 wipe_owned_path(dir);
101 }
102 let sides = std::mem::take(&mut ledger.side_channels);
103 for p in sides {
104 wipe_owned_path(&p);
105 }
106 let not_before = ledger.started_at.unwrap_or_else(std::time::SystemTime::now);
108 let chrome_pid = ledger.chrome_pid; let _ = crate::residual::scavenge_owned_chromium_tmp_orphans(
110 profile.as_deref(),
111 chrome_pid,
112 not_before,
113 );
114 #[cfg(windows)]
115 if ledger.windows_job_handle != 0 {
116 crate::win_job::close_job(ledger.windows_job_handle);
117 ledger.windows_job_handle = 0;
118 }
119 }
120 }
121}
122
123fn wipe_owned_path(path: &std::path::Path) {
125 if !path.exists() {
126 return;
127 }
128 if path.is_dir() {
129 let _ = std::fs::remove_dir_all(path);
130 } else {
131 let _ = std::fs::remove_file(path);
132 }
133}
134
135pub fn mark_profile_dir(life: &Lifecycle, dir: Option<PathBuf>) {
137 if let Some(dir) = dir {
138 if let Ok(mut ledger) = life.ledger.lock() {
139 ledger.profile_dir = Some(dir);
140 }
141 }
142}
143
144pub fn mark_side_channel(life: &Lifecycle, path: PathBuf) {
146 if let Ok(mut ledger) = life.ledger.lock() {
147 if !ledger.side_channels.iter().any(|p| p == &path) {
148 ledger.side_channels.push(path);
149 }
150 }
151}
152
153impl Default for Lifecycle {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl Drop for Lifecycle {
160 fn drop(&mut self) {
161 self.finalize();
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn finalize_is_idempotent() {
171 let lc = Lifecycle::new();
172 lc.finalize();
173 lc.finalize();
174 assert!(lc.finalize_done.load(Ordering::SeqCst));
175 }
176}