mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 11:22:04 +00:00
Merge 74c437b0fe into 7d01aa0417
This commit is contained in:
commit
99e6d9c07b
14
.changes/use-plugin-cleanups-and-kill-tree.md
Normal file
14
.changes/use-plugin-cleanups-and-kill-tree.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
'@tauri-apps/tauri': 'minor:enhance'
|
||||
---
|
||||
|
||||
Introduce plugin-level cleanup hooks and centralize process-tree termination logic.
|
||||
|
||||
This change:
|
||||
- Adds `kill_process_tree` helper to the runtime for cross-platform process-tree shutdown.
|
||||
- Adds a new `cleanup_before_exit` lifecycle hook to plugins and wires it so plugin authors
|
||||
can handle sidecar shutdown without core runtime logic.
|
||||
- Removes hardcoded sidecar-draining from the runtime and delegates shutdown behavior to plugins.
|
||||
|
||||
This allows plugins (such as the shell plugin) to manage their own sidecar processes cleanly
|
||||
and improves extensibility of the Tauri runtime. Fixes #14360.
|
||||
@ -28,6 +28,41 @@ pub struct DevChild {
|
||||
|
||||
impl DevProcess for DevChild {
|
||||
fn kill(&self) -> std::io::Result<()> {
|
||||
if let Some(pid) = self.dev_child.id() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::process::Command;
|
||||
let ps = format!(
|
||||
"function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid -ErrorAction SilentlyContinue }}; Kill-Tree {}",
|
||||
pid
|
||||
);
|
||||
let _ = Command::new("powershell")
|
||||
.arg("-NoProfile")
|
||||
.arg("-Command")
|
||||
.arg(ps)
|
||||
.status();
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::process::Command;
|
||||
let sh = format!(r#"
|
||||
getcpid() {{
|
||||
for cpid in $(pgrep -P "$1" 2>/dev/null || true); do
|
||||
getcpid "$cpid"
|
||||
echo "$cpid"
|
||||
done
|
||||
}}
|
||||
for p in $(getcpid {pid}); do
|
||||
kill -9 "$p" 2>/dev/null || true
|
||||
done
|
||||
kill -9 {pid} 2>/dev/null || true
|
||||
"#, pid = pid);
|
||||
|
||||
let _ = Command::new("sh").arg("-c").arg(sh).status();
|
||||
}
|
||||
}
|
||||
|
||||
self.dev_child.kill()?;
|
||||
self.manually_killed_app.store(true, Ordering::SeqCst);
|
||||
Ok(())
|
||||
|
||||
@ -499,6 +499,8 @@ impl<R: Runtime> AppHandle<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Removes the plugin with the given name.
|
||||
///
|
||||
/// # Examples
|
||||
@ -1035,6 +1037,13 @@ macro_rules! shared_app_impl {
|
||||
for (_, webview) in self.manager.webviews() {
|
||||
webview.resources_table().clear();
|
||||
}
|
||||
// run plugin cleanup hooks so plugins can perform shutdown tasks (e.g. stop sidecars)
|
||||
self
|
||||
.manager
|
||||
.plugins
|
||||
.lock()
|
||||
.unwrap()
|
||||
.cleanup_before_exit(self.app_handle());
|
||||
}
|
||||
|
||||
/// Gets the invoke key that must be referenced when using [`crate::webview::InvokeRequest`].
|
||||
|
||||
@ -104,6 +104,15 @@ pub trait Plugin<R: Runtime>: Send {
|
||||
#[allow(unused_variables)]
|
||||
fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {}
|
||||
|
||||
/// Callback invoked when the application is performing cleanup before exit.
|
||||
///
|
||||
/// Plugins can use this hook to perform any process shutdown/cleanup they need
|
||||
/// to do before the runtime exits (for example, killing sidecars or stopping
|
||||
/// background tasks). This hook is executed inside `App::cleanup_before_exit` during application shutdown.
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn cleanup_before_exit(&mut self, app: &AppHandle<R>) {}
|
||||
|
||||
/// Extend commands to [`crate::Builder::invoke_handler`].
|
||||
#[allow(unused_variables)]
|
||||
fn extend_api(&mut self, invoke: Invoke<R>) -> bool {
|
||||
@ -979,6 +988,15 @@ impl<R: Runtime> PluginStore<R> {
|
||||
.for_each(|plugin| plugin.on_event(app, event))
|
||||
}
|
||||
|
||||
/// Runs the cleanup_before_exit hook for all plugins in the store.
|
||||
pub(crate) fn cleanup_before_exit(&mut self, app: &AppHandle<R>) {
|
||||
self.store.iter_mut().for_each(|plugin| {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::trace_span!("plugin::hooks::cleanup_before_exit", name = plugin.name()).entered();
|
||||
plugin.cleanup_before_exit(app)
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the plugin `extend_api` hook if it exists. Returns whether the invoke message was handled or not.
|
||||
///
|
||||
/// The message is not handled when the plugin exists **and** the command does not.
|
||||
|
||||
@ -128,3 +128,98 @@ fn restart_macos_app(current_binary: &std::path::Path, env: &Env) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill a process and its descendant process tree (best-effort).
|
||||
///
|
||||
/// On Windows this function prefers the built-in `taskkill /T /PID <pid> /F`
|
||||
/// utility which can terminate a process tree. If `taskkill` is unavailable
|
||||
/// or returns a non-zero exit status (for example due to permissions), the
|
||||
/// function falls back to a PowerShell-based recursive traversal which mirrors
|
||||
/// the previous implementation.
|
||||
///
|
||||
/// On Unix-like systems a small shell function using `pgrep -P` is used to
|
||||
/// collect child PIDs and send `SIGKILL` to descendants and the root PID.
|
||||
///
|
||||
/// Note: terminating processes is inherently best-effort and may fail for
|
||||
/// protected or system processes, or when the caller lacks sufficient
|
||||
/// privileges. Callers should handle and log any errors returned by this
|
||||
/// function.
|
||||
|
||||
// TODO: Move this helper into the `process` plugin in the plugins-workspace repo.
|
||||
pub fn kill_process_tree(pid: u32) -> std::io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
// Prefer the built-in `taskkill` utility on Windows which can terminate a process
|
||||
// tree with `/T`. If that fails (permissions, not found, or non-zero exit), fall
|
||||
// back to a PowerShell-based recursive stop that mirrors the previous behavior.
|
||||
let pid_s = pid.to_string();
|
||||
|
||||
if let Ok(status) = Command::new("taskkill")
|
||||
.args(&["/T", "/PID", &pid_s, "/F"]) // /F to force termination
|
||||
.status()
|
||||
{
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
// If taskkill returned non-zero, fall through to try PowerShell.
|
||||
}
|
||||
|
||||
// Fallback: Use PowerShell to recursively find and stop child processes, then stop the root.
|
||||
// This mirrors the approach used elsewhere in the project (tauri-cli) and preserves
|
||||
// behavior on systems where taskkill isn't available or failed due to permissions.
|
||||
let ps = format!(
|
||||
"function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid -ErrorAction SilentlyContinue }}; Kill-Tree {}",
|
||||
pid
|
||||
);
|
||||
|
||||
let status = Command::new("powershell")
|
||||
.arg("-NoProfile")
|
||||
.arg("-Command")
|
||||
.arg(ps)
|
||||
.status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("kill-tree failed: powershell exited with status: {}", status),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::process::Command;
|
||||
|
||||
// On Unix, recursively collect children via pgrep -P and kill them. We use a small
|
||||
// shell function to traverse descendants and then kill them. Use SIGKILL to ensure
|
||||
// termination (best effort).
|
||||
let sh = format!(r#"
|
||||
getcpid() {{
|
||||
for cpid in $(pgrep -P "$1" 2>/dev/null || true); do
|
||||
getcpid "$cpid"
|
||||
echo "$cpid"
|
||||
done
|
||||
}}
|
||||
for p in $(getcpid {pid}); do
|
||||
kill -9 "$p" 2>/dev/null || true
|
||||
done
|
||||
kill -9 {pid} 2>/dev/null || true
|
||||
"#, pid = pid);
|
||||
|
||||
let status = Command::new("sh").arg("-c").arg(sh).status()?;
|
||||
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("sh kill-tree failed with status: {}", status),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user