Skip to content

feat(shell): add process group spawn option (fix #1332)#3351

Open
rterence wants to merge 9 commits into
tauri-apps:v2from
rterence:feat/shell-process-group
Open

feat(shell): add process group spawn option (fix #1332)#3351
rterence wants to merge 9 commits into
tauri-apps:v2from
rterence:feat/shell-process-group

Conversation

@rterence

@rterence rterence commented Mar 19, 2026

Copy link
Copy Markdown

Summary

  • Adds a processGroup boolean option to the shell plugin's spawn command
  • When enabled, spawns the child in a new process group (POSIX) or job object (Windows) using the process-wrap crate
  • Killing the child process now kills the entire process tree, fixing orphaned grandchild processes (e.g. PyInstaller-wrapped apps)

Closes #1332
cc. @FabianLars

Changes

  • Rust (process/mod.rs): Command::set_process_group() builder method, GroupChild type with platform-specific kill (Unix: killpg, Windows: job object termination via process-wrap)
  • Rust (commands.rs): processGroup field on CommandOptions, plumbed through prepare_cmd
  • TypeScript (index.ts): processGroup?: boolean on SpawnOptions
  • Dependencies: process-wrap 8.2 (std feature), libc 0.2 (unix)

Testing

  • All 12 unit tests pass, including 5 new process group tests
  • PyInstaller simulation tests reproduce the exact issue scenario: without processGroup killing the wrapper orphans the grandchild; with processGroup killing the wrapper also kills the grandchild
  • Windows not tested locally (uses process-wrap JobObject with polling try_wait)

Demo

The video below demonstrates the fix using a real PyInstaller-bundled Python app spawned from the Tauri example app's Shell view.

First half — without processGroup (the bug):

  1. The demo app is spawned with "Process Group" unchecked
  2. The kill button is pressed
  3. Activity Monitor shows the demo_app processes persist — the grandchild is orphaned

Second half — with processGroup enabled (the fix):

  1. The demo app is spawned with "Process Group" checked
  2. The kill button is pressed
  3. Activity Monitor shows all demo_app processes are terminated
process-group-demo.mp4

Add a `processGroup` boolean option to the shell plugin's spawn command.
When enabled, the child process is spawned in its own process group (POSIX)
or job object (Windows) using the `process-wrap` crate, so that killing
the child also kills the entire process tree.

This fixes the issue where programs like PyInstaller wrappers spawn a
child process that gets orphaned when Tauri kills the parent.
Add end-to-end tests that reproduce the exact scenario from issue tauri-apps#1332:
a wrapper process (like PyInstaller's bootloader) spawns a grandchild.

- Without process_group: killing the wrapper orphans the grandchild
- With process_group: killing the wrapper also kills the grandchild
@rterence rterence requested a review from a team as a code owner March 19, 2026 17:04
@Noremac11800

Copy link
Copy Markdown

Awesome additions @rterence! May I ask what's holding this up? This would go a long way to simplify one of my projects.

@rterence

Copy link
Copy Markdown
Author

Awesome additions @rterence! May I ask what's holding this up? This would go a long way to simplify one of my projects.
@FabianLars can I get a review on this please?

@socket-security

socket-security Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​process-wrap@​8.2.110010093100100

View full report

@FabianLars

Copy link
Copy Markdown
Member

Sorry sorry. My backlog is a bit insane ATM. Will try to take a look this week.

@FabianLars FabianLars left a comment

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.

didn't try it yet, just some messy thoughts before going to bed 😅

Comment thread plugins/shell/src/process/mod.rs Outdated
Comment thread plugins/shell/Cargo.toml Outdated
Comment thread plugins/shell/src/process/mod.rs Outdated
#[derive(Debug)]
pub struct CommandChild {
inner: Arc<SharedChild>,
inner: ChildKind,

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 wonder if process-wrap supports spawning CommandWrap that did not add any wrappers? In that case i assume it should be possible to drop SharedChild completely in favor of CommandWrap? (Edit: Or rather, get rid of just the enum since we still use SharedChild on Unix here?) Then we'd just have to conditionally add the wrapper instead of having the whole child be the switch.

idk if i'm asking for too much here for this pr 😅

@rterence rterence Jun 24, 2026

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.

Same constraint as below. The Windows child can't be reduced to a raw Child without losing tree-kill. Happy to tidy the spawn path in a follow-up leaving a TODO?

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 can't follow my own text there lol so let me ask it differently. Can we drop SharedChild on Unix and use the same struct GroupChild as on Windows? If not, what is process-wrap's ProcessGroupChild missing here?

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.

Addressed in e8d0801. Dropped SharedChild and the ChildKind enum entirely, so there's now a single process-wrap child type for both platforms and process_group is just an optional wrapper. Lmk what you think, happy to tidy up the PR.

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.

@FabianLars are you happy with this if I tidy up the PR and attach an updated demo?

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.

@FabianLars if you get a moment could I get another review on this please? Or if this is being fixed elsewhere can you link me? Thanks 😄

Comment thread plugins/shell/src/process/mod.rs Outdated
Address review feedback on tauri-apps#3351:

- Remove `#[cfg(any(unix, windows))]` gates on `ChildKind::ProcessGroup`,
  the kill/pid match arms, and the spawn block, plus the now-dead
  `#[cfg(not(any(unix, windows)))]` fallback. That cfg only excluded wasm,
  which this plugin never builds for.
- Set `default-features = false` on process-wrap and enable only the
  features actually used: std, process-group (unix), creation-flags and
  job-object (windows). Drops kill-on-drop, process-session, and tracing.
rterence added a commit to rterence/plugins-workspace that referenced this pull request Jun 24, 2026
Address review feedback on tauri-apps#3351:

- Remove `#[cfg(any(unix, windows))]` gates on `ChildKind::ProcessGroup`,
  the kill/pid match arms, and the spawn block, plus the now-dead
  `#[cfg(not(any(unix, windows)))]` fallback. That cfg only excluded wasm,
  which this plugin never builds for.
- Set `default-features = false` on process-wrap and enable only the
  features actually used: std, process-group (unix), creation-flags and
  job-object (windows). Drops kill-on-drop, process-session, and tracing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread plugins/shell/src/process/mod.rs Outdated
Comment on lines +87 to +96
fn kill(&self) -> std::io::Result<()> {
// SAFETY: killpg is a standard POSIX syscall. The pgid was obtained from
// the child's PID, which equals its PGID since it was spawned as a group leader.
let ret = unsafe { libc::killpg(self.pgid, libc::SIGKILL) };
if ret == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}

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.

rterence added 2 commits June 24, 2026 20:26
…d changefile

- Add `tracing` feature to process-wrap: with default-features = false its
  Windows job_object.rs calls `debug\!` unconditionally while the import is
  gated behind `#[cfg(feature = "tracing")]`, failing the Windows build with
  "cannot find macro `debug`".
- Reformat process/mod.rs assertions to satisfy `cargo fmt --check`.
- Reindent the process-wrap features array to 2 spaces for taplo.
- Use covector package keys (`shell` / `shell-js`) in the change file so the
  check-change-files and covector status jobs pass.
Collapse the ChildKind enum and the per-platform GroupChild types into a
single process-wrap-backed child. The command always spawns through
StdCommandWrap; the process_group flag is now just an optional wrapper
rather than a switch into a separate child type.

This drops the shared_child dependency and resolves two review notes:
the Unix path no longer hand-rolls killpg, and kill() now goes through
process-wrap's kill (start_kill + wait), which reaps the entire process
group instead of only signalling it.

A background thread polls try_wait to surface the exit status, since
process-wrap's wrappers only expose &mut self wait methods (the reason
SharedChild was used on Unix in the first place).
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Package Changes Through 0dc519e

There are 6 changes which include log with minor, log-js with minor, positioner with patch, positioner-js with patch, shell with minor, shell-js with minor

Planned Package Versions

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

package current next
api-example 2.0.44 2.0.45
api-example-js 2.0.40 2.0.41
log 2.8.0 2.9.0
log-js 2.8.0 2.9.0
positioner 2.3.2 2.3.3
positioner-js 2.3.2 2.3.3
shell 2.3.5 2.4.0
shell-js 2.3.5 2.4.0

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


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

@rterence rterence force-pushed the feat/shell-process-group branch from e8d0801 to 0dc519e Compare June 27, 2026 21:24
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.

[shell] Add option to spawn command in process group

3 participants