mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-22 20:06:18 +02:00
refactor: better browser process handling
This commit is contained in:
Vendored
+1
@@ -112,6 +112,7 @@
|
||||
"peerconnection",
|
||||
"pids",
|
||||
"pixbuf",
|
||||
"pkill",
|
||||
"plasmohq",
|
||||
"platformdirs",
|
||||
"prefs",
|
||||
|
||||
+367
-458
File diff suppressed because it is too large
Load Diff
@@ -214,6 +214,207 @@ end try
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn kill_browser_process_impl(
|
||||
pid: u32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Attempting to kill browser process with PID: {pid}");
|
||||
|
||||
// For Chromium-based browsers, use immediate aggressive termination
|
||||
// Chromium browsers are notoriously difficult to kill on macOS due to process spawning
|
||||
|
||||
// Step 1: Immediate SIGKILL on main process (no graceful shutdown for Chromium)
|
||||
println!("Starting immediate SIGKILL for PID: {pid}");
|
||||
let _ = Command::new("kill")
|
||||
.args(["-KILL", &pid.to_string()])
|
||||
.output();
|
||||
|
||||
// Step 2: Comprehensive process tree termination using multiple methods simultaneously
|
||||
let _ = kill_chromium_process_tree_aggressive(pid).await;
|
||||
|
||||
// Step 2.5: Nuclear option - kill all Chromium processes by name pattern
|
||||
let _ = kill_all_chromium_processes_by_name().await;
|
||||
|
||||
// Step 3: Use multiple kill strategies in parallel
|
||||
let pid_str = pid.to_string();
|
||||
|
||||
// Kill by parent PID with SIGKILL
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-P", &pid_str])
|
||||
.output();
|
||||
|
||||
// Kill by process group with SIGKILL
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-g", &pid_str])
|
||||
.output();
|
||||
|
||||
// Kill by session ID
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-s", &pid_str])
|
||||
.output();
|
||||
|
||||
// Wait briefly for initial termination
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
|
||||
// Step 4: Verify and retry with pattern-based killing for common Chromium process names
|
||||
use sysinfo::{Pid, System};
|
||||
let system = System::new_all();
|
||||
|
||||
// Check if main process still exists
|
||||
if system.process(Pid::from(pid as usize)).is_some() {
|
||||
println!("Main process {pid} still running, using pattern-based termination");
|
||||
|
||||
// Kill by common Chromium process patterns
|
||||
let chromium_patterns = [
|
||||
"Chrome", "Chromium", "Brave", "chrome", "chromium", "brave",
|
||||
"Google Chrome", "Brave Browser", "Chrome Helper", "Chromium Helper"
|
||||
];
|
||||
|
||||
for pattern in &chromium_patterns {
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-f", pattern])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Final aggressive cleanup - kill any remaining processes
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(300)).await;
|
||||
|
||||
// One more round of comprehensive killing
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-P", &pid_str])
|
||||
.output();
|
||||
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-g", &pid_str])
|
||||
.output();
|
||||
|
||||
// Final verification with extended wait
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
let system = System::new_all();
|
||||
|
||||
if system.process(Pid::from(pid as usize)).is_some() {
|
||||
// Last resort: try system kill command with different signals
|
||||
println!("Process {pid} extremely persistent, trying system-level termination");
|
||||
|
||||
let _ = Command::new("/bin/kill")
|
||||
.args(["-KILL", &pid_str])
|
||||
.output();
|
||||
|
||||
let _ = Command::new("/usr/bin/killall")
|
||||
.args(["-KILL", "-m", "Chrome"])
|
||||
.output();
|
||||
|
||||
let _ = Command::new("/usr/bin/killall")
|
||||
.args(["-KILL", "-m", "Chromium"])
|
||||
.output();
|
||||
|
||||
let _ = Command::new("/usr/bin/killall")
|
||||
.args(["-KILL", "-m", "Brave"])
|
||||
.output();
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
||||
let system = System::new_all();
|
||||
|
||||
if system.process(Pid::from(pid as usize)).is_some() {
|
||||
println!("WARNING: Process {pid} could not be terminated despite aggressive attempts");
|
||||
// Don't return error - let the UI update anyway since we tried everything
|
||||
}
|
||||
}
|
||||
|
||||
println!("Aggressive browser termination completed for PID: {pid}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Helper function to kill process tree (Chromium browsers often spawn child processes)
|
||||
async fn kill_chromium_process_tree_aggressive(pid: u32) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Killing comprehensive process tree for PID: {pid}");
|
||||
|
||||
// Get all descendant processes using recursive process tree discovery
|
||||
let descendant_pids = get_all_descendant_pids(pid).await;
|
||||
println!("Found {} descendant processes to terminate", descendant_pids.len());
|
||||
|
||||
// Kill all descendants first (reverse order - children before parents)
|
||||
for &desc_pid in descendant_pids.iter().rev() {
|
||||
if desc_pid != pid {
|
||||
println!("Terminating descendant process: {desc_pid}");
|
||||
let _ = Command::new("kill")
|
||||
.args(["-KILL", &desc_pid.to_string()])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
|
||||
// No delay for initial termination
|
||||
|
||||
// Force kill any remaining descendants
|
||||
for &desc_pid in descendant_pids.iter().rev() {
|
||||
if desc_pid != pid {
|
||||
let _ = Command::new("kill")
|
||||
.args(["-KILL", &desc_pid.to_string()])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
|
||||
// Also use pkill as a backup to catch any processes we might have missed
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-P", &pid.to_string()])
|
||||
.output();
|
||||
|
||||
// On macOS, also try killing by process group for Chromium browsers
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-g", &pid.to_string()])
|
||||
.output();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Helper function to kill all Chromium-related processes by name patterns
|
||||
async fn kill_all_chromium_processes_by_name() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Killing all Chromium-related processes by name patterns");
|
||||
|
||||
let chromium_patterns = [
|
||||
"Chrome", "Chromium", "Brave", "chrome", "chromium", "brave",
|
||||
"Google Chrome", "Brave Browser", "Chrome Helper", "Chromium Helper"
|
||||
];
|
||||
|
||||
for pattern in &chromium_patterns {
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-KILL", "-f", pattern])
|
||||
.output();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Recursively find all descendant processes
|
||||
async fn get_all_descendant_pids(parent_pid: u32) -> Vec<u32> {
|
||||
use sysinfo::{Pid, System};
|
||||
|
||||
let system = System::new_all();
|
||||
let mut descendants = Vec::new();
|
||||
let mut to_check = vec![parent_pid];
|
||||
let mut checked = std::collections::HashSet::new();
|
||||
|
||||
while let Some(current_pid) = to_check.pop() {
|
||||
if checked.contains(¤t_pid) {
|
||||
continue;
|
||||
}
|
||||
checked.insert(current_pid);
|
||||
|
||||
// Find direct children of current_pid
|
||||
for (pid, process) in system.processes() {
|
||||
let pid_u32 = pid.as_u32();
|
||||
if let Some(parent) = process.parent() {
|
||||
if parent.as_u32() == current_pid && !checked.contains(&pid_u32) {
|
||||
descendants.push(pid_u32);
|
||||
to_check.push(pid_u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
descendants
|
||||
}
|
||||
|
||||
pub async fn open_url_in_existing_browser_tor_mullvad(
|
||||
profile: &BrowserProfile,
|
||||
url: &str,
|
||||
@@ -455,39 +656,6 @@ end try
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn kill_browser_process_impl(
|
||||
pid: u32,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Attempting to kill browser process with PID: {pid}");
|
||||
|
||||
// First try SIGTERM (graceful shutdown)
|
||||
let output = Command::new("kill")
|
||||
.args(["-TERM", &pid.to_string()])
|
||||
.output()
|
||||
.map_err(|e| format!("Failed to execute kill command: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
// If SIGTERM fails, try SIGKILL (force kill)
|
||||
let output = Command::new("kill")
|
||||
.args(["-KILL", &pid.to_string()])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(
|
||||
format!(
|
||||
"Failed to kill process {}: {}",
|
||||
pid,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Successfully killed browser process with PID: {pid}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
+48
-2
@@ -159,6 +159,46 @@ export default function Home() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Function to check and sync profile running states with actual process status
|
||||
const syncProfileRunningStates = useCallback(
|
||||
async (profiles: BrowserProfile[]) => {
|
||||
try {
|
||||
const statusChecks = profiles.map(async (profile) => {
|
||||
try {
|
||||
const isRunning = await invoke<boolean>("check_browser_status", {
|
||||
profile,
|
||||
});
|
||||
return { id: profile.id, isRunning };
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to check status for profile ${profile.name}:`,
|
||||
error,
|
||||
);
|
||||
return { id: profile.id, isRunning: false };
|
||||
}
|
||||
});
|
||||
|
||||
const statuses = await Promise.all(statusChecks);
|
||||
|
||||
// Update running profiles state based on actual status
|
||||
setRunningProfiles((prev) => {
|
||||
const next = new Set(prev);
|
||||
statuses.forEach(({ id, isRunning }) => {
|
||||
if (isRunning) {
|
||||
next.add(id);
|
||||
} else {
|
||||
next.delete(id);
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to sync profile running states:", error);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Simple profiles loader without updates check (for use as callback)
|
||||
const loadProfiles = useCallback(async () => {
|
||||
try {
|
||||
@@ -167,13 +207,16 @@ export default function Home() {
|
||||
);
|
||||
setProfiles(profileList);
|
||||
|
||||
// Check and sync profile running status after loading profiles
|
||||
await syncProfileRunningStates(profileList);
|
||||
|
||||
// Check for missing binaries after loading profiles
|
||||
await checkMissingBinaries();
|
||||
} catch (err: unknown) {
|
||||
console.error("Failed to load profiles:", err);
|
||||
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
||||
}
|
||||
}, [checkMissingBinaries]);
|
||||
}, [checkMissingBinaries, syncProfileRunningStates]);
|
||||
|
||||
const [processingUrls, setProcessingUrls] = useState<Set<string>>(new Set());
|
||||
|
||||
@@ -219,6 +262,9 @@ export default function Home() {
|
||||
);
|
||||
setProfiles(profileList);
|
||||
|
||||
// Check and sync profile running status after loading profiles
|
||||
await syncProfileRunningStates(profileList);
|
||||
|
||||
// Check for updates after loading profiles
|
||||
await checkForUpdates();
|
||||
await checkMissingBinaries();
|
||||
@@ -226,7 +272,7 @@ export default function Home() {
|
||||
console.error("Failed to load profiles:", err);
|
||||
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
||||
}
|
||||
}, [checkForUpdates, checkMissingBinaries]);
|
||||
}, [checkForUpdates, checkMissingBinaries, syncProfileRunningStates]);
|
||||
|
||||
useAppUpdateNotifications();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user