mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-26 19:22:09 +02:00
refactor: better browser process handling
This commit is contained in:
Vendored
+1
@@ -112,6 +112,7 @@
|
|||||||
"peerconnection",
|
"peerconnection",
|
||||||
"pids",
|
"pids",
|
||||||
"pixbuf",
|
"pixbuf",
|
||||||
|
"pkill",
|
||||||
"plasmohq",
|
"plasmohq",
|
||||||
"platformdirs",
|
"platformdirs",
|
||||||
"prefs",
|
"prefs",
|
||||||
|
|||||||
+365
-456
File diff suppressed because it is too large
Load Diff
@@ -214,6 +214,207 @@ end try
|
|||||||
Ok(())
|
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(
|
pub async fn open_url_in_existing_browser_tor_mullvad(
|
||||||
profile: &BrowserProfile,
|
profile: &BrowserProfile,
|
||||||
url: &str,
|
url: &str,
|
||||||
@@ -455,39 +656,6 @@ end try
|
|||||||
|
|
||||||
Ok(())
|
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")]
|
#[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)
|
// Simple profiles loader without updates check (for use as callback)
|
||||||
const loadProfiles = useCallback(async () => {
|
const loadProfiles = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -167,13 +207,16 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
setProfiles(profileList);
|
setProfiles(profileList);
|
||||||
|
|
||||||
|
// Check and sync profile running status after loading profiles
|
||||||
|
await syncProfileRunningStates(profileList);
|
||||||
|
|
||||||
// Check for missing binaries after loading profiles
|
// Check for missing binaries after loading profiles
|
||||||
await checkMissingBinaries();
|
await checkMissingBinaries();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.error("Failed to load profiles:", err);
|
console.error("Failed to load profiles:", err);
|
||||||
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
||||||
}
|
}
|
||||||
}, [checkMissingBinaries]);
|
}, [checkMissingBinaries, syncProfileRunningStates]);
|
||||||
|
|
||||||
const [processingUrls, setProcessingUrls] = useState<Set<string>>(new Set());
|
const [processingUrls, setProcessingUrls] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
@@ -219,6 +262,9 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
setProfiles(profileList);
|
setProfiles(profileList);
|
||||||
|
|
||||||
|
// Check and sync profile running status after loading profiles
|
||||||
|
await syncProfileRunningStates(profileList);
|
||||||
|
|
||||||
// Check for updates after loading profiles
|
// Check for updates after loading profiles
|
||||||
await checkForUpdates();
|
await checkForUpdates();
|
||||||
await checkMissingBinaries();
|
await checkMissingBinaries();
|
||||||
@@ -226,7 +272,7 @@ export default function Home() {
|
|||||||
console.error("Failed to load profiles:", err);
|
console.error("Failed to load profiles:", err);
|
||||||
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
setError(`Failed to load profiles: ${JSON.stringify(err)}`);
|
||||||
}
|
}
|
||||||
}, [checkForUpdates, checkMissingBinaries]);
|
}, [checkForUpdates, checkMissingBinaries, syncProfileRunningStates]);
|
||||||
|
|
||||||
useAppUpdateNotifications();
|
useAppUpdateNotifications();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user