Skip to content

Add kill-tree helper and runtime sidecar PID registry#14443

Open
sakina1303 wants to merge 5 commits into
tauri-apps:devfrom
sakina1303:fix
Open

Add kill-tree helper and runtime sidecar PID registry#14443
sakina1303 wants to merge 5 commits into
tauri-apps:devfrom
sakina1303:fix

Conversation

@sakina1303
Copy link
Copy Markdown

Adds a best-effort process-tree killer and a lightweight runtime sidecar PID registry, ensuring that descendant processes spawned by sidecars are properly terminated when their parent sidecar is killed.

This resolves the core issue described in #14360 by introducing both API and runtime cleanup mechanisms for sidecar process management.

Details:

  • tauri::process::kill_process_tree(pid: u32) — POSIX shell & PowerShell based, best-effort.
  • Sidecar PID registry via AppHandle::register_sidecar(pid) / unregister_sidecar(pid) for cleanup on exit.
  • App::cleanup_before_exit() drains registry and calls the kill helper.
  • CLI dev-run now attempts process-tree cleanup when stopping dev children.

Files Changed:

  • process.rs: Added kill_process_tree() (POSIX + PowerShell).
  • mod.rs: Added sidecar PID tracking + helper methods.
  • app.rs: Added register/unregister APIs; updated cleanup.
  • desktop.rs: Added kill-tree logic in dev-run path.
  • CHANGELOG.md / PR_DRAFT.md: Updated with notes.

Testing:

  • [ ✅ ] cargo test -p tauri, all tests passed.
  • [ ✅ ] Manual: verified registry and cleanup invoke kill helper.
  • Runtime cleanup is opt-in:
    Call app_handle.register_sidecar(child.id() as u32) after spawning.

Fixes #14360

@sakina1303 sakina1303 requested a review from a team as a code owner November 10, 2025 12:29
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Nov 10, 2025

Package Changes Through ed8d624

There are 1 changes which include @tauri-apps/api with patch

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
@tauri-apps/api 2.9.0 2.9.1

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

Comment thread crates/tauri/src/app.rs Outdated
Comment thread crates/tauri-cli/src/interface/rust/desktop.rs
Copy link
Copy Markdown
Contributor

@Legend-Master Legend-Master left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

Comment thread crates/tauri/CHANGELOG.md Outdated
Comment thread crates/tauri/PR_DRAFT.md Outdated
/// background tasks). This is guaranteed to run from the thread performing
/// the app cleanup/exit sequence.
#[allow(unused_variables)]
fn cleanup_before_exit(&mut self, app: &AppHandle<R>) {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would also want to implement this for the TauriPlugin so that tauri::plugin::Builder could have access to this API

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for sure! I’ll add this to the TauriPlugin trait as well so it’s accessible via tauri::plugin::Builder.

Comment thread crates/tauri/src/app.rs Outdated
@Legend-Master Legend-Master added this to the 2.10 milestone Nov 12, 2025
Comment thread crates/tauri/src/plugin.rs Outdated
/// protected or system processes, or when the caller lacks sufficient
/// privileges. Callers should handle and log any errors returned by this
/// function.
pub fn kill_process_tree(pid: u32) -> std::io::Result<()> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is a generic function and not bound to this app instance imo this should be moved into https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/process (and in theory that plugin's exit and restart should probably be part of the app core plugin instead but that's a discussion for another day).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be okay if I leave a TODO for now and move it in a follow-up PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we at least have to decide how to proceed here because once this is added in tauri we cannot remove it without a major release since removing apis is a breaking change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I move kill_process_tree to the process plugin now as you suggested? I’ll update this PR to avoid a future breaking change.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that would make sense, yes. we can always move it back if needed (that way around works)

@Legend-Master
Copy link
Copy Markdown
Contributor

Legend-Master commented Nov 13, 2025

I think we have now gathered a few different problems around this now:

  • Only the side car process is killed on app exit, not the entire process tree
    • Just from testing, it seems like some of the processes are killed while some others are not???
  • tauri dev doesn't kill side cars (it kills the app forcefully by sending SIGKILL on mac and linux, TerminateProcess on Windows to cargo)
    • It seems to me that it does kill the side car but not always the entire process tree???
  • NSIS installers don't kill side cars, they kill the app forcefully through TerminateProcess (the Wix .msi installers do, they send WM_QUERYENDSESSION to the app which is now handled after fix(windows): emit LoopDestroyed on WM_ENDSESSION tao#1126 no, I thought they do but what actually happened is just it killed the tray icon by sending WM_CLOSE)
  • Updater plugin exits the app through std::process::exit which doesn't trigger the Exit event so side cars are not killed

My brain is basically fried right now, no more processes 😭

@FabianLars
Copy link
Copy Markdown
Member

  1. and perhaps 2) should imo be fixed via [shell] Add option to spawn command in process group plugins-workspace#1332 and not by trying to kill a process tree. prob doesn't help with the rest though.

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 2, 2025

I think we have now gathered a few different problems around this now:

  • Only the side car process is killed on app exit, not the entire process tree

    • Just from testing, it seems like some of the processes are killed while some others are not???
  • tauri dev doesn't kill side cars (it kills the app forcefully by sending SIGKILL on mac and linux, TerminateProcess on Windows to cargo)

    • It seems to me that it does kill the side car but not always the entire process tree???
  • NSIS installers don't kill side cars, they kill the app forcefully through TerminateProcess (the Wix .msi installers do, they send WM_QUERYENDSESSION to the app which is now handled after fix(windows): emit LoopDestroyed on WM_ENDSESSION tao#1126 no, I thought they do but what actually happened is just it killed the tray icon by sending WM_CLOSE)

  • Updater plugin exits the app through std::process::exit which doesn't trigger the Exit event so side cars are not killed

My brain is basically fried right now, no more processes 😭

I fixed point 1, 2 in my(our) project. 3 and 4 not test on my machine but has no community report related. With sidecar, I hold a custom strcture to wrap ProcessChild and give impl Drop for it to handle kill when droping. The entire app(which is main process) listen on system signal and excute tauri exiting and custom behavior, it will kill itself and its sidecar. They works well both in tauri dev and release build.

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 2, 2025

I think we have now gathered a few different problems around this now:

  • Only the side car process is killed on app exit, not the entire process tree

    • Just from testing, it seems like some of the processes are killed while some others are not???
  • tauri dev doesn't kill side cars (it kills the app forcefully by sending SIGKILL on mac and linux, TerminateProcess on Windows to cargo)

    • It seems to me that it does kill the side car but not always the entire process tree???
  • NSIS installers don't kill side cars, they kill the app forcefully through TerminateProcess (the Wix .msi installers do, they send WM_QUERYENDSESSION to the app which is now handled after fix(windows): emit LoopDestroyed on WM_ENDSESSION tao#1126 no, I thought they do but what actually happened is just it killed the tray icon by sending WM_CLOSE)

  • Updater plugin exits the app through std::process::exit which doesn't trigger the Exit event so side cars are not killed

My brain is basically fried right now, no more processes 😭

I fixed point 1, 2 in my(our) project. 3 and 4 not test on my machine but has no community report related. With sidecar, I hold a custom strcture to wrap ProcessChild and give impl Drop for it to handle kill when droping. The entire app(which is main process) listen on system signal and excute Apphandle::Exit, it will kill itself and its sidecar. They works well.

We found it's hard to fully trust tauri exit behavior, and external listen system signal mannuly in clash-verge-rev/crates/signal. But behavior of macOS system shutdown signal seems hooked by tauri, can only processing with tauri::RunEvent::Exit otherwise will skip signal hanlde, but ubuntu and other linux works fine.

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 2, 2025

This PR using system shell. For Windows example in some case, user can not invoke system powershell due to their machine permission setting or even disable powershell usage. We might not want to handle PID with shell diretcly, and there are potential security problems whether if cross-platform.

@Legend-Master
Copy link
Copy Markdown
Contributor

With sidecar, I hold a custom strcture to wrap ProcessChild and give impl Drop for it to handle kill when droping.

I think this is more or less something we should provide in shell plugin?

We found it's hard to fully trust tauri exit behavior

Could you explain about this a bit more?

This PR using system shell.

Not the biggest fan of this either

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 4, 2025

I think this is more or less something we should provide in shell plugin?

Yes, and the auto-cleanup or likely behavior can be toggled via a field setting whether if spawn or runtime. We did not provide this behavior before, might destroy downstream program behavior if enable by default.

Could you explain about this a bit more?

Months ago, did not remember too much details.

  • The sidecar plugin's process will not be killed when exiting cli tauri dev from terminal.
  • We tried handle with RunEvent::ExitRequested and RunEvent::Exit to reset system network settings when system shudown. None of linux, macOS and windows tasks works as expected. Might just we did not learned the proper way.

More, hanlding with resue tauri::asyncruntime to excute exiting or system shutdown operations in Windows will produce more likehood to failed or skipped. With a new tokio spawn would be totally fixed on that and works fine across Windows, macOS and Linux. clash-verge-rev/clash-verge-rev#5533 (comment) (Chinese)

@Legend-Master
Copy link
Copy Markdown
Contributor

We tried handle with RunEvent::ExitRequested and RunEvent::Exit to reset system network settings when system shudown. None of linux, macOS and windows tasks works as expected. Might just we did not learned the proper way.

RunEvent::Exit event should now fire on system shutdown on Windows (tauri-apps/tao#1126), not on macOS and Linux yet though

More, hanlding with resue tauri::asyncruntime to excute exiting or system shutdown operations in Windows will produce more likehood to failed or skipped.

Hmm, that sounds weird, the tauri async runtime by default should be very much the same as using a tokio one directly

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 4, 2025

should be very much the same as using a tokio one directly

Our program continuosly handle differents tauri commands, data-related processing, system setting processing. Might be a little more complex than genral program. The async_runtime::spawn The singleton async runtime used by Tauri and exposed to users. that we already used for spawn some inner tasks. A new tokio runtime would not be stuggle with tauri.

/// async_runtime::spawn
pub fn spawn<F>(task: F) -> JoinHandle<F::Output>
where
  F: Future + Send + 'static,
  F::Output: Send + 'static,
{
  let runtime = RUNTIME.get_or_init(default_runtime);
  runtime.spawn(task)
}

@Tunglies
Copy link
Copy Markdown
Contributor

Tunglies commented Dec 4, 2025

not on macOS and Linux yet though

Wierd on my test on macOS, signal_hook can not handle shutdown signal, but SIGTERM, SIGTERM interminal or standalone were fine. Macos shutdown handle only works with

#[cfg(target_os = "macos")]
        tauri::RunEvent::Exit => async_runtime::block_on(async {
            some_clean_up()
        }),

Linux works with competely works with signal_hook. Seemed tauri hooked macOS shutdown signal and use tauri::RunEvent::Exit for case.

@FabianLars
Copy link
Copy Markdown
Member

Macos shutdown handle only works with

Not gonna lie, this sounds like something macOS would. Not sending posix signals in favor of the NSApplication delegate bullshit would be such an Apple move 🙃
I'm somewhat sure it's not us doing that.

@Qodo-Free-For-OSS
Copy link
Copy Markdown

Hi, App::cleanup_before_exit() unconditionally unwraps the plugins mutex lock; if plugin code panicked earlier and poisoned the mutex, shutdown will panic instead of performing best-effort cleanup.

Severity: action required | Category: reliability

How to fix: Handle poisoned mutex gracefully

Agent prompt to fix - you can give this to your LLM of choice:

Issue description

cleanup_before_exit should be best-effort. Panicking on a poisoned plugins mutex defeats shutdown cleanup.

Issue Context

This is invoked during application teardown.

Fix Focus Areas

  • crates/tauri/src/app.rs[1042-1048]
    • Replace .lock().unwrap() with logic that handles poisoning, e.g. match lock() then into_inner() on PoisonError, or skip plugin cleanup with a log.
  • crates/tauri/src/plugin.rs[991-998]
    • (Optional) consider guarding individual plugin hook panics if the runtime’s policy is to continue best-effort cleanup.

Found by Qodo code review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Add kill tree for sidecar

6 participants