refactor: better browser process handling

This commit is contained in:
zhom
2025-08-17 12:44:20 +04:00
parent 69348a101e
commit 10f8061acf
4 changed files with 617 additions and 493 deletions
+1
View File
@@ -112,6 +112,7 @@
"peerconnection",
"pids",
"pixbuf",
"pkill",
"plasmohq",
"platformdirs",
"prefs",
File diff suppressed because it is too large Load Diff
+201 -33
View File
@@ -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(&current_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
View File
@@ -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();