From 2748d20d959a5265e0e653339773a0d4035946b1 Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Tue, 25 Nov 2025 18:48:10 +0900 Subject: [PATCH] add root supervisor in desktop app --- Cargo.lock | 2 ++ apps/desktop/src-tauri/Cargo.toml | 3 ++ apps/desktop/src-tauri/src/lib.rs | 19 ++++++++-- apps/desktop/src-tauri/src/supervisor.rs | 30 ++++++++++++++++ plugins/listener/src/lib.rs | 41 +++++++++++++++++++--- plugins/listener/src/supervisor.rs | 33 +++++++++++++++++ plugins/local-stt/src/lib.rs | 13 +++++-- plugins/local-stt/src/server/supervisor.rs | 17 ++++++--- 8 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/src-tauri/src/supervisor.rs create mode 100644 plugins/listener/src/supervisor.rs diff --git a/Cargo.lock b/Cargo.lock index 7b19b5053c..b7d335b021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3925,6 +3925,8 @@ name = "desktop" version = "0.0.0" dependencies = [ "aspasia", + "ractor", + "ractor-supervisor", "sentry", "serde", "serde_json", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 58809b6812..e7e4cef42a 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -64,6 +64,9 @@ serde_json = { workspace = true } strum = { workspace = true, features = ["derive"] } tracing = { workspace = true } +ractor = { workspace = true } +ractor-supervisor = { workspace = true } + aspasia = "0.2.1" tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 188b8473b9..586ee4ac34 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -2,6 +2,7 @@ mod commands; mod ext; mod store; mod subtitle; +mod supervisor; use ext::*; use store::*; @@ -13,6 +14,12 @@ use tauri_plugin_windows::{AppWindow, WindowsPluginExt}; pub async fn main() { tauri::async_runtime::set(tokio::runtime::Handle::current()); + let (root_supervisor, _root_supervisor_handle) = match supervisor::spawn_root_supervisor().await + { + Some((supervisor, handle)) => (Some(supervisor), Some(handle)), + None => (None, None), + }; + let sentry_client = { let dsn = option_env!("SENTRY_DSN"); @@ -57,9 +64,7 @@ pub async fn main() { .plugin(tauri_plugin_db2::init()) .plugin(tauri_plugin_tracing::init()) .plugin(tauri_plugin_hooks::init()) - .plugin(tauri_plugin_listener::init()) .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_local_stt::init()) .plugin(tauri_plugin_permissions::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_deep_link::init()) @@ -77,6 +82,16 @@ pub async fn main() { .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_store2::init()) .plugin(tauri_plugin_windows::init()) + .plugin(tauri_plugin_listener::init( + tauri_plugin_listener::InitOptions { + parent_supervisor: root_supervisor.as_ref().map(|s| s.get_cell()), + }, + )) + .plugin(tauri_plugin_local_stt::init( + tauri_plugin_local_stt::InitOptions { + parent_supervisor: root_supervisor.as_ref().map(|s| s.get_cell()), + }, + )) .plugin(tauri_plugin_autostart::init( tauri_plugin_autostart::MacosLauncher::LaunchAgent, Some(vec!["--background"]), diff --git a/apps/desktop/src-tauri/src/supervisor.rs b/apps/desktop/src-tauri/src/supervisor.rs new file mode 100644 index 0000000000..74581b954a --- /dev/null +++ b/apps/desktop/src-tauri/src/supervisor.rs @@ -0,0 +1,30 @@ +use ractor::concurrency::Duration; +use ractor::ActorRef; +use ractor_supervisor::dynamic::{ + DynamicSupervisor, DynamicSupervisorMsg, DynamicSupervisorOptions, +}; + +pub type SupervisorRef = ActorRef; +pub type SupervisorHandle = tokio::task::JoinHandle<()>; + +const ROOT_SUPERVISOR_NAME: &str = "root_supervisor"; + +pub async fn spawn_root_supervisor() -> Option<(SupervisorRef, SupervisorHandle)> { + let options = DynamicSupervisorOptions { + max_children: Some(10), + max_restarts: 50, + max_window: Duration::from_secs(60), + reset_after: Some(Duration::from_secs(30)), + }; + + match DynamicSupervisor::spawn(ROOT_SUPERVISOR_NAME.to_string(), options).await { + Ok((supervisor_ref, handle)) => { + tracing::info!("root_supervisor_spawned"); + Some((supervisor_ref, handle)) + } + Err(e) => { + tracing::error!("failed_to_spawn_root_supervisor: {:?}", e); + None + } + } +} diff --git a/plugins/listener/src/lib.rs b/plugins/listener/src/lib.rs index b804b7a5cc..d010c76a19 100644 --- a/plugins/listener/src/lib.rs +++ b/plugins/listener/src/lib.rs @@ -1,3 +1,5 @@ +use ractor::ActorCell; +use ractor_supervisor::dynamic::DynamicSupervisorMsg; use tauri::Manager; use tokio::sync::Mutex; @@ -7,17 +9,26 @@ mod error; mod events; mod ext; pub mod fsm; +mod supervisor; pub use error::*; pub use events::*; pub use ext::*; +pub use supervisor::{SupervisorHandle, SupervisorRef, SUPERVISOR_NAME}; const PLUGIN_NAME: &str = "listener"; -pub type SharedState = Mutex; +pub type SharedState = std::sync::Arc>; pub struct State { - app: tauri::AppHandle, + pub app: tauri::AppHandle, + pub listener_supervisor: Option>, + pub supervisor_handle: Option, +} + +#[derive(Default)] +pub struct InitOptions { + pub parent_supervisor: Option, } impl State { @@ -48,7 +59,7 @@ fn make_specta_builder() -> tauri_specta::Builder { .error_handling(tauri_specta::ErrorHandlingMode::Result) } -pub fn init() -> tauri::plugin::TauriPlugin { +pub fn init(options: InitOptions) -> tauri::plugin::TauriPlugin { let specta_builder = make_specta_builder(); tauri::plugin::Builder::new(PLUGIN_NAME) @@ -58,9 +69,29 @@ pub fn init() -> tauri::plugin::TauriPlugin { let app_handle = app.app_handle().clone(); - let state: SharedState = Mutex::new(State { app: app_handle }); + let state: SharedState = std::sync::Arc::new(Mutex::new(State { + app: app_handle, + listener_supervisor: None, + supervisor_handle: None, + })); + + app.manage(state.clone()); + + let parent = options.parent_supervisor.clone(); + tauri::async_runtime::spawn(async move { + match supervisor::spawn_listener_supervisor(parent).await { + Ok((supervisor, handle)) => { + let mut guard = state.lock().await; + guard.listener_supervisor = Some(supervisor); + guard.supervisor_handle = Some(handle); + tracing::info!("listener_supervisor_spawned"); + } + Err(e) => { + tracing::error!("failed_to_spawn_listener_supervisor: {:?}", e); + } + } + }); - app.manage(state); Ok(()) }) .build() diff --git a/plugins/listener/src/supervisor.rs b/plugins/listener/src/supervisor.rs new file mode 100644 index 0000000000..5b2ef7ee90 --- /dev/null +++ b/plugins/listener/src/supervisor.rs @@ -0,0 +1,33 @@ +use ractor::{ActorCell, ActorProcessingErr, ActorRef}; +use ractor_supervisor::dynamic::{ + DynamicSupervisor, DynamicSupervisorMsg, DynamicSupervisorOptions, +}; + +pub type SupervisorRef = ActorRef; +pub type SupervisorHandle = tokio::task::JoinHandle<()>; + +pub const SUPERVISOR_NAME: &str = "listener_supervisor"; + +fn make_supervisor_options() -> DynamicSupervisorOptions { + DynamicSupervisorOptions { + max_children: Some(10), + max_restarts: 50, + max_window: ractor::concurrency::Duration::from_secs(60), + reset_after: Some(ractor::concurrency::Duration::from_secs(30)), + } +} + +pub async fn spawn_listener_supervisor( + parent: Option, +) -> Result<(SupervisorRef, SupervisorHandle), ActorProcessingErr> { + let options = make_supervisor_options(); + + let (supervisor_ref, handle) = + DynamicSupervisor::spawn(SUPERVISOR_NAME.to_string(), options).await?; + + if let Some(parent_cell) = parent { + supervisor_ref.get_cell().link(parent_cell); + } + + Ok((supervisor_ref, handle)) +} diff --git a/plugins/local-stt/src/lib.rs b/plugins/local-stt/src/lib.rs index 2932368bcb..576cead099 100644 --- a/plugins/local-stt/src/lib.rs +++ b/plugins/local-stt/src/lib.rs @@ -1,4 +1,4 @@ -use ractor::ActorRef; +use ractor::{ActorCell, ActorRef}; use ractor_supervisor::dynamic::DynamicSupervisorMsg; use std::collections::HashMap; use tauri::{Manager, Wry}; @@ -14,6 +14,7 @@ mod types; pub use error::*; pub use ext::*; pub use model::*; +pub use server::supervisor::{SupervisorRef, SUPERVISOR_NAME}; pub use server::*; pub use types::*; @@ -27,6 +28,11 @@ pub struct State { pub supervisor_handle: Option, } +#[derive(Default)] +pub struct InitOptions { + pub parent_supervisor: Option, +} + const PLUGIN_NAME: &str = "local-stt"; fn make_specta_builder() -> tauri_specta::Builder { @@ -48,7 +54,7 @@ fn make_specta_builder() -> tauri_specta::Builder { .error_handling(tauri_specta::ErrorHandlingMode::Result) } -pub fn init() -> tauri::plugin::TauriPlugin { +pub fn init(options: InitOptions) -> tauri::plugin::TauriPlugin { let specta_builder = make_specta_builder(); tauri::plugin::Builder::new(PLUGIN_NAME) @@ -67,8 +73,9 @@ pub fn init() -> tauri::plugin::TauriPlugin { app.manage(state.clone()); + let parent = options.parent_supervisor.clone(); tauri::async_runtime::spawn(async move { - match server::supervisor::spawn_stt_supervisor().await { + match server::supervisor::spawn_stt_supervisor(parent).await { Ok((supervisor, handle)) => { let mut guard = state.lock().await; guard.stt_supervisor = Some(supervisor); diff --git a/plugins/local-stt/src/server/supervisor.rs b/plugins/local-stt/src/server/supervisor.rs index 5165f2b817..31f72379d6 100644 --- a/plugins/local-stt/src/server/supervisor.rs +++ b/plugins/local-stt/src/server/supervisor.rs @@ -16,18 +16,27 @@ pub const INTERNAL_STT_ACTOR_NAME: &str = "internal_stt"; pub const EXTERNAL_STT_ACTOR_NAME: &str = "external_stt"; pub const SUPERVISOR_NAME: &str = "stt_supervisor"; -pub async fn spawn_stt_supervisor( -) -> Result<(ActorRef, crate::SupervisorHandle), ActorProcessingErr> { - let options = DynamicSupervisorOptions { +fn make_supervisor_options() -> DynamicSupervisorOptions { + DynamicSupervisorOptions { max_children: Some(1), max_restarts: 100, max_window: Duration::from_secs(60 * 3), reset_after: Some(Duration::from_secs(30)), - }; + } +} + +pub async fn spawn_stt_supervisor( + parent: Option, +) -> Result<(ActorRef, crate::SupervisorHandle), ActorProcessingErr> { + let options = make_supervisor_options(); let (supervisor_ref, handle) = DynamicSupervisor::spawn(SUPERVISOR_NAME.to_string(), options).await?; + if let Some(parent_cell) = parent { + supervisor_ref.get_cell().link(parent_cell); + } + Ok((supervisor_ref, handle)) }