Replies: 7 comments 39 replies
-
|
Command.spawn() returns a Child. That child should be Send&Sync so you can move it around and store it quite easily. You can then call For storing the child you can use stuff like once_cell or tauri's built-in State. Alternatively if this is a sidecar you can use tauris built-in Command::new_sidecar instead which uses the same creation_flag under the hood. This would have the sideeffect of tauri automatically trying to clean up those spawned children on app exit, while still providing a similar interface so you can kill it yourself. |
Beta Was this translation helpful? Give feedback.
-
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod functions;
use std::process::Command;
use std::sync::mpsc::{sync_channel, Receiver};
use std::sync::Mutex;
use std::thread;
use command_group::{CommandGroup, GroupChild};
use tauri::api::process::Command as TCommand;
use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt};
use tauri::WindowEvent;
fn start_backend(receiver: Receiver<i32>) {
let t = TCommand::new_sidecar("windxapi")
.expect("启动API服务器失败");
let mut group = Command::from(t).group_spawn().expect("启动API失败");
thread::spawn(move || {
loop{
let mut s = receiver.recv();
if s.unwrap()==-1 {
group.kill().expect("关闭API失败");
}
}
});
}
fn main() {
let (tx,rx) = sync_channel(1);
start_backend(rx);
tauri::Builder::default()
.on_window_event(move |event| match event.event() {
WindowEvent::Destroyed => {
println!("准备关闭后台API");
tx.send(-1).expect("发送关闭信号失败");
println!("已关闭后台API");
}
_ => {}
})
// .invoke_handler(tauri::generate_handler![start_backend,terminate_backend,terminate_pid])
.run(tauri::generate_context!())
.expect("error while running tauri application");
} |
Beta Was this translation helpful? Give feedback.
-
|
I recommend using the kill binary. use std::process::{Child, Command};
fn kill_process(child: &mut Child) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(unix)]
{
let pid = child.id().to_string();
println!("Sending INT signal to process with PID: {}", pid);
let mut kill = StdCommand::new("kill")
.args(["-s", "SIGINT", &pid])
.spawn()?;
kill.wait()?;
}
#[cfg(windows)]
{
let pid = child.id().to_string();
println!("Sending taskkill to process with PID: {}", pid);
let mut kill = StdCommand::new("taskkill")
.args(["/PID", &pid, "/F"])
.spawn()?;
kill.wait()?;
}
Ok(())
}
fn main() {
let child = Command::new("your_process")
.spawn()
.expect("Failed to start process");
// Simulate some work
std::thread::sleep(std::time::Duration::from_secs(5));
match kill_process(&child) {
Ok(_) => println!("Process killed successfully."),
Err(e) => eprintln!("Failed to kill process: {}", e),
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Thanks to #3273 (comment) and #7558 (comment) I found a solution. commands.rsfn kill_process(pid: &str) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(unix)] {
println!("Sending INT signal to process with PID: {}", pid);
let mut kill = Command::new("kill")
.args(["-s", "SIGINT", &pid])
.spawn()?;
kill.wait()?;
}
#[cfg(windows)] {
println!("Sending taskkill to process with PID: {}", pid);
let mut kill = StdCommand::new("taskkill")
.args(["/PID", &pid, "/F"])
.spawn()?;
kill.wait()?;
}
Ok(())
}
#[tauri::command]
pub async fn quit_letsdane(pid: &str) -> Result<(), String> {
match kill_process(pid) {
Ok(_) => {
println!("Process killed successfully.");
Ok(())
},
Err(e) => {
eprintln!("Failed to kill process: {}", e);
Err(e.to_string())
}
}
}
#[tauri::command]
pub async fn start_letsdane(app: tauri::AppHandle) {
let child = Command::new("/absolute/path/to/my/sidecar/executable-aarch64-apple-darwin")
.args([
"-r", "127.0.0.1:5350",
"-addr", ":5555",
"-skip-dnssec",
"-skip-icann",
"-conf", "/absolute/path/to/.config"
])
.spawn()
.expect("Failed to start process");
let pid = child.id().to_string();
app.emit("letsdane-pid", Payload { message: pid.to_string().into() }).unwrap();
}main.rs// ...
.invoke_handler(tauri::generate_handler![
commands::quit_letsdane,
commands::start_letsdane
])
// ...+page.svelte $: letsdanePID = 0;
type Payload = {
message: string;
};
const subMenu7 = await MenuItem.new({
action: async() => {
// TODO
// : stop hnsd
/// stop letsdane process
await invoke("quit_letsdane", { pid: `${letsdanePID}` });
await exit(0);
},
text: "Quit"
});
async function rustBackendListener() {
await listen<Payload>("letsdane-pid", (event) => {
console.log("Event triggered from rust!\nPayload: " + event.payload.message);
letsdanePID = Number(event.payload.message);
console.log(event.payload.message);
});
}
onMount(async() => {
await rustBackendListener();
await invoke("start_letsdane");
console.log(">>> letsdanePID", letsdanePID);
});My app will be open-source at some point but this is the gist. |
Beta Was this translation helpful? Give feedback.
-
|
This seems to work for me in Tauri v2. Note that I use PyInstaller onefile which spawns 2 processes. Therefore, I also use STDIN to get some signal to terminate the other process. use std::sync::{Arc, Mutex};
use tauri_plugin_shell::ShellExt;
use tauri::Manager;
use tauri::path::BaseDirectory;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.setup(|app| {
let resource_path = app
.path().resolve("resources/info.log", BaseDirectory::Resource)?;
let sidecar_command = app.shell().sidecar("client2").unwrap().args([resource_path]);
let ( _rx, sidecar_child) = sidecar_command
.spawn()
.expect("Failed to spawn sidecar");
// Wrap the child process in Arc<Mutex<>> for shared access
let child = Arc::new(Mutex::new(Some(sidecar_child)));
// Clone the Arc to move into the async task
let child_clone = Arc::clone(&child);
let window = app.get_webview_window("main").unwrap();
window.on_window_event( move |event| {
if let tauri::WindowEvent::CloseRequested { .. } = event {
let mut child_lock = child_clone.lock().unwrap();
if let Some(mut child_process) = child_lock.take() {
if let Err(e) = child_process.write("Exit message from Rust\n".as_bytes())
{
println!("Fail to send to stdin of Python: {}", e);
}
if let Err(e) = child_process.kill() {
eprintln!("Failed to kill child process: {}", e);
}
}
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
} |
Beta Was this translation helpful? Give feedback.
-
|
In my solution, let desc = Arc::new(scrflow::TowerConfig::default().create());
tauri::Builder::default()
.manage(Arc::downgrade(&desc)) // Tauri does not drop managed states
.invoke_handler(tauri::generate_handler![start, stop, terminate])
.on_window_event(move |_, ev| {
if matches!(ev, WindowEvent::Destroyed) {
let desc = Arc::clone(&desc);
let desc = Arc::into_raw(desc);
unsafe {
// Drop desc
Arc::decrement_strong_count(desc);
let _ = Arc::from_raw(desc);
}
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application"); |
Beta Was this translation helpful? Give feedback.
-
|
In tauri v2 for Music Caster's rewrite that I'm working on, I created my own sidecar utility file that uses the There were three major obstacles in coming to this code. The first was figuring out how to manage state, which is easy once you decipher that you need mutability (i.e. wrap any struct that requires mutability with a Mutex). The second, which is harder, is that the child process is being killed, meaning that you need to somehow turn a Something into a Nothing. To "solve" this, you can just state that the child is an Option, which makes sense if you think about how the program could theoretically continue even after the child is killed. Thus, we use mod sidecar_utils;
use crate::sidecar_utils::{MusicCasterDaemon, SidecarProcess};
.setup(|app| {
let app_handle = app.handle().clone();
let process = SidecarProcess::<MusicCasterDaemon>::new(&app_handle)?;
app_handle.manage(process);
}
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|_app_handle, event| match event {
tauri::RunEvent::ExitRequested { api, .. } => {
let mc_child_state = _app_handle.state::<Mutex<SidecarProcess<MusicCasterDaemon>>>();
let _ = mc_child_state.inner().lock().unwrap().kill();
}
_ => {}
});sidecar_utils.rsuse std::collections::VecDeque;
use std::marker::PhantomData;
use std::sync::Mutex;
use sysinfo::{Pid, Signal, System};
use tauri::{self, Manager};
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
pub trait SidecarConfig {
fn binary_name() -> &'static str;
fn args(app_handle: &tauri::AppHandle) -> Vec<String>;
}
// TODO: create a SidecarManager that can manage spawning and despawning sidecars based on Enums
/// A generic wrapper for managing sidecar processes with automatic cleanup
/// Usage:
/// let process = SidecarProcess::<MusicCasterDaemon>::new(&app_handle)?;
#[derive(Debug)]
pub struct SidecarProcess<T: SidecarConfig> {
child: Option<CommandChild>,
_phantom: PhantomData<T>,
}
impl<T: SidecarConfig> SidecarProcess<T> {
/// Create and spawn a new sidecar process
pub fn new(app_handle: &tauri::AppHandle) -> Result<Mutex<Self>, String> {
let sidecar_command = app_handle
.shell()
.sidecar(T::binary_name())
.map_err(|e| format!("Failed to create sidecar command: {}", e))?
.args(T::args(app_handle));
let (mut rx, child) = sidecar_command
.spawn()
.map_err(|e| format!("Failed to spawn {}: {}", T::binary_name(), e))?;
// Spawn async task to handle process output
let binary_name = T::binary_name().to_string();
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line_bytes) => {
let line = String::from_utf8_lossy(&line_bytes);
log::info!("[{}] {}", binary_name, line);
}
CommandEvent::Stderr(line_bytes) => {
let line = String::from_utf8_lossy(&line_bytes);
log::error!("[{} Error] {}", binary_name, line);
}
CommandEvent::Error(err) => {
log::error!("[{}] Error: {}", binary_name, err);
}
CommandEvent::Terminated(payload) => {
log::info!("[{}] Terminated with code: {:?}", binary_name, payload.code);
break;
}
_ => {}
}
}
});
Ok(Mutex::new(SidecarProcess {
child: Some(child),
_phantom: PhantomData,
}))
}
/// Get the process ID of the sidecar
pub fn pid(&self) -> Option<u32> {
self.child.as_ref().map(|c| c.pid())
}
/// Kill the sidecar process and all its descendants
pub fn kill(&mut self) -> Result<(), String> {
if let Some(child) = self.child.take() {
kill_process_tree(child.pid())
} else {
Ok(())
}
}
}
impl<T: SidecarConfig> Drop for SidecarProcess<T> {
fn drop(&mut self) {
if self.child.is_some() {
let _ = self.kill();
}
}
}
fn kill_process_tree(pid: u32) -> Result<(), String> {
let mut system = System::new_all();
system.refresh_all();
let root_pid = Pid::from_u32(pid);
// Collect all descendant PIDs
let to_kill = collect_descendants(&system, root_pid);
// Kill all processes (children first, then parent)
for &pid in &to_kill {
if let Some(process) = system.process(pid) {
process.kill_with(Signal::Kill);
}
}
Ok(())
}
fn collect_descendants(system: &System, root_pid: Pid) -> VecDeque<Pid> {
let mut descendants = VecDeque::new();
let mut queue = VecDeque::new();
queue.push_back(root_pid);
while let Some(current_pid) = queue.pop_front() {
descendants.push_front(current_pid);
// Find all direct children of current_pid
for (child_pid, process) in system.processes() {
if let Some(parent_pid) = process.parent() {
if parent_pid == current_pid {
queue.push_back(*child_pid);
}
}
}
}
descendants
}
pub struct MusicCasterDaemon {}
impl SidecarConfig for MusicCasterDaemon {
fn binary_name() -> &'static str {
"music-caster-daemon"
}
fn args(app_handle: &tauri::AppHandle) -> Vec<String> {
let app_args: Vec<String> = std::env::args().skip(1).collect();
let app_data_dir = app_handle
.path()
.app_data_dir()
.map_err(|e| format!("Failed to get app data directory: {}", e))
.unwrap();
std::fs::create_dir_all(&app_data_dir)
.map_err(|e| format!("Failed to create app data directory: {}", e))
.unwrap();
let db_path = app_data_dir.join("music_caster.db").display().to_string();
let settings_path = app_data_dir.join("settings.json").display().to_string();
log::info!("settings path: {}", &settings_path);
log::info!("db_path: {}", &db_path);
let mut sidecar_args = vec![
"--db-path".to_string(),
db_path,
"--settings-path".to_string(),
settings_path,
];
sidecar_args.extend(app_args);
sidecar_args
}
} |
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
-
Hi, thanks for this wonderful library. I want to know how to kill process started with
use std::process::CommandThis is my code to start tauri app as well as a flask server:
Beta Was this translation helpful? Give feedback.
All reactions