Skip to content

Instantly share code, notes, and snippets.

@lucasshiva
Last active August 22, 2025 02:17
Show Gist options
  • Save lucasshiva/d4e8bd1d123bf552052ab70920f594ac to your computer and use it in GitHub Desktop.
Save lucasshiva/d4e8bd1d123bf552052ab70920f594ac to your computer and use it in GitHub Desktop.
Managing sidecar lifecycle in Tauri 2.
use std::net::TcpListener;
use std::sync::{Arc, Mutex};
use tauri::Manager;
use tauri_plugin_shell::ShellExt;
fn get_available_port() -> Result<u16, std::io::Error> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let port = listener.local_addr()?.port();
Ok(port)
}
#[derive(Default)]
struct AppState {
port: Option<u16>,
}
#[tauri::command]
// Expose the port to the frontend.
fn get_service_port(state: tauri::State<'_, Mutex<AppState>>) -> Option<u16> {
let state = state.lock().unwrap();
state.port
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
// Focus the current window when trying to open a second instance.
let _ = app
.get_webview_window("main")
.expect("No main window")
.set_focus();
}))
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_opener::init())
.manage(Mutex::new(AppState::default()))
.setup(|app| {
let port = get_available_port().expect("Failed to get available port");
let sidecar_command = app
.shell()
.sidecar("signalr_service")?
.arg(port.to_string());
match sidecar_command.spawn() {
Err(e) => {
eprintln!("Failed to spawn sidecar: {}", e);
}
Ok((_rx, child)) => {
let child = Arc::new(Mutex::new(Some(child)));
let child_clone = Arc::clone(&child);
// We only save the port if spawn succeded.
// We could also check if the service is actually running.
let state = app.state::<Mutex<AppState>>();
let mut state = state.lock().unwrap();
state.port = Some(port);
// Kill service on window close
if let Some(window) = app.get_webview_window("main") {
window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { .. } = event {
let mut child_lock = child_clone.lock().unwrap();
if let Some(child_process) = child_lock.take() {
if let Err(e) = child_process.kill() {
eprintln!("Failed to kill sidecar process: {}", e);
} else {
println!("Sidecar process terminated succesfully");
}
}
}
});
}
}
}
Ok(())
})
.invoke_handler(tauri::generate_handler![get_service_port])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment