Skip to content

Commit 4bdc406

Browse files
authored
feat(core): kill sidecar child processes on App drop, closes #1896 (#1932)
1 parent 9ddd9a9 commit 4bdc406

3 files changed

Lines changed: 75 additions & 15 deletions

File tree

.changes/child-process-cleanup.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Kill child processes spawned with `tauri::api::process::Command` on `tauri::App` drop. Can be skipped with `tauri::Builder#skip_cleanup_on_drop`.

core/tauri/src/api/process/command.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
io::{BufRead, BufReader, Write},
88
path::PathBuf,
99
process::{Command as StdCommand, Stdio},
10-
sync::Arc,
10+
sync::{Arc, Mutex},
1111
};
1212

1313
#[cfg(unix)]
@@ -24,6 +24,22 @@ use serde::Serialize;
2424
use shared_child::SharedChild;
2525
use tauri_utils::platform;
2626

27+
type ChildStore = Arc<Mutex<HashMap<u32, Arc<SharedChild>>>>;
28+
29+
fn commands() -> &'static ChildStore {
30+
use once_cell::sync::Lazy;
31+
static STORE: Lazy<ChildStore> = Lazy::new(Default::default);
32+
&STORE
33+
}
34+
35+
/// Kill all child process created with [`Command`].
36+
/// By default it's called before the [`crate::App`] exits.
37+
pub fn kill_children() {
38+
for child in commands().lock().unwrap().values() {
39+
let _ = child.kill();
40+
}
41+
}
42+
2743
/// Payload for the `Terminated` command event.
2844
#[derive(Debug, Clone, Serialize)]
2945
pub struct TerminatedPayload {
@@ -220,6 +236,8 @@ impl Command {
220236
let child_ = child.clone();
221237
let guard = Arc::new(RwLock::new(()));
222238

239+
commands().lock().unwrap().insert(child.id(), child.clone());
240+
223241
let (tx, rx) = channel(1);
224242

225243
let tx_ = tx.clone();
@@ -252,6 +270,7 @@ impl Command {
252270
let _ = match child_.wait() {
253271
Ok(status) => {
254272
guard.write().await;
273+
commands().lock().unwrap().remove(&child_.id());
255274
tx.send(CommandEvent::Terminated(TerminatedPayload {
256275
code: status.code(),
257276
#[cfg(windows)]

core/tauri/src/app.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,21 @@ crate::manager::default_args! {
128128
///
129129
/// This type implements [`Manager`] which allows for manipulation of global application items.
130130
pub struct App<P: Params> {
131-
runtime: P::Runtime,
131+
runtime: Option<P::Runtime>,
132132
manager: WindowManager<P>,
133+
#[cfg(shell_execute)]
134+
cleanup_on_drop: bool,
135+
}
136+
}
137+
138+
impl<P: Params> Drop for App<P> {
139+
fn drop(&mut self) {
140+
#[cfg(shell_execute)]
141+
{
142+
if self.cleanup_on_drop {
143+
crate::api::process::kill_children();
144+
}
145+
}
133146
}
134147
}
135148

@@ -140,7 +153,7 @@ impl<P: Params> ManagerBase<P> for App<P> {
140153
}
141154

142155
fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
143-
RuntimeOrDispatch::Runtime(&self.runtime)
156+
RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
144157
}
145158
}
146159

@@ -180,7 +193,7 @@ impl<P: Params> App<P> {
180193
/// Gets a handle to the application instance.
181194
pub fn handle(&self) -> AppHandle<P> {
182195
AppHandle {
183-
runtime_handle: self.runtime.handle(),
196+
runtime_handle: self.runtime.as_ref().unwrap().handle(),
184197
manager: self.manager.clone(),
185198
}
186199
}
@@ -202,7 +215,7 @@ impl<P: Params> App<P> {
202215
/// }
203216
#[cfg(any(target_os = "windows", target_os = "macos"))]
204217
pub fn run_iteration(&mut self) -> crate::runtime::RunIteration {
205-
self.runtime.run_iteration()
218+
self.runtime.as_mut().unwrap().run_iteration()
206219
}
207220
}
208221

@@ -315,6 +328,9 @@ where
315328
/// System tray event handlers.
316329
#[cfg(feature = "system-tray")]
317330
system_tray_event_listeners: Vec<SystemTrayEventListener<Args<E, L, MID, TID, A, R>>>,
331+
332+
#[cfg(shell_execute)]
333+
cleanup_on_drop: bool,
318334
}
319335

320336
impl<E, L, MID, TID, A, R> Builder<E, L, MID, TID, A, R>
@@ -345,6 +361,8 @@ where
345361
system_tray: Vec::new(),
346362
#[cfg(feature = "system-tray")]
347363
system_tray_event_listeners: Vec::new(),
364+
#[cfg(shell_execute)]
365+
cleanup_on_drop: true,
348366
}
349367
}
350368

@@ -572,6 +590,15 @@ where
572590
self
573591
}
574592

593+
/// Skips Tauri cleanup on [`App`] drop. Useful if your application has multiple [`App`] instances.
594+
///
595+
/// The cleanup calls [`crate::api::process::kill_children`] so you may want to call that function before exiting the application.
596+
#[cfg(shell_execute)]
597+
pub fn skip_cleanup_on_drop(mut self) -> Self {
598+
self.cleanup_on_drop = false;
599+
self
600+
}
601+
575602
/// Builds the application.
576603
#[allow(clippy::type_complexity)]
577604
pub fn build(mut self, context: Context<A>) -> crate::Result<App<Args<E, L, MID, TID, A, R>>> {
@@ -630,8 +657,10 @@ where
630657
}
631658

632659
let mut app = App {
633-
runtime: R::new()?,
660+
runtime: Some(R::new()?),
634661
manager,
662+
#[cfg(shell_execute)]
663+
cleanup_on_drop: self.cleanup_on_drop,
635664
};
636665

637666
app.manager.initialize_plugins(&app)?;
@@ -647,7 +676,7 @@ where
647676

648677
for pending in self.pending_windows {
649678
let pending = app.manager.prepare_window(pending, &pending_labels)?;
650-
let detached = app.runtime.create_window(pending)?;
679+
let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
651680
let _window = app.manager.attach_window(detached);
652681
#[cfg(feature = "updater")]
653682
if main_window.is_none() {
@@ -665,6 +694,8 @@ where
665694
let ids = get_menu_ids(&self.system_tray);
666695
app
667696
.runtime
697+
.as_ref()
698+
.unwrap()
668699
.system_tray(
669700
system_tray_icon.expect("tray icon not found; please configure it on tauri.conf.json"),
670701
self.system_tray,
@@ -674,14 +705,18 @@ where
674705
let app_handle = app.handle();
675706
let ids = ids.clone();
676707
let listener = Arc::new(std::sync::Mutex::new(listener));
677-
app.runtime.on_system_tray_event(move |event| {
678-
let app_handle = app_handle.clone();
679-
let menu_item_id = ids.get(&event.menu_item_id).unwrap().clone();
680-
let listener = listener.clone();
681-
crate::async_runtime::spawn(async move {
682-
listener.lock().unwrap()(&app_handle, SystemTrayEvent { menu_item_id });
708+
app
709+
.runtime
710+
.as_mut()
711+
.unwrap()
712+
.on_system_tray_event(move |event| {
713+
let app_handle = app_handle.clone();
714+
let menu_item_id = ids.get(&event.menu_item_id).unwrap().clone();
715+
let listener = listener.clone();
716+
crate::async_runtime::spawn(async move {
717+
listener.lock().unwrap()(&app_handle, SystemTrayEvent { menu_item_id });
718+
});
683719
});
684-
});
685720
}
686721
}
687722

@@ -690,7 +725,8 @@ where
690725

691726
/// Runs the configured Tauri application.
692727
pub fn run(self, context: Context<A>) -> crate::Result<()> {
693-
self.build(context)?.runtime.run();
728+
let mut app = self.build(context)?;
729+
app.runtime.take().unwrap().run();
694730
Ok(())
695731
}
696732
}

0 commit comments

Comments
 (0)