mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
fix: ensure RunEvent::Exit is triggered on restart (#12313)
* fix: AppHandle::restart() may not send RunEvent::Exit event * docs: add changelog: `AppHandle::restart()` may not send `RunEvent::Exit` event before exiting the application. * style: cargo fmt * avoid deadlock on main thread * do not let the restart be prevented leads to a deadlock currently * Apply suggestions from code review * lint * do not emit RunEvent on main thread * re-export RESTART_EXIT_CODE --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
5
.changes/restart-may-not-publish-exit-event.md
Normal file
5
.changes/restart-may-not-publish-exit-event.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch:bug
|
||||
---
|
||||
|
||||
`AppHandle::restart()` now waits for `RunEvent::Exit` to be delivered before restarting the application.
|
||||
@@ -42,7 +42,8 @@ use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
sync::{mpsc::Sender, Arc, MutexGuard},
|
||||
sync::{mpsc::Sender, Arc, Mutex, MutexGuard},
|
||||
thread::ThreadId,
|
||||
};
|
||||
|
||||
use crate::{event::EventId, runtime::RuntimeHandle, Event, EventTarget};
|
||||
@@ -73,14 +74,19 @@ pub const RESTART_EXIT_CODE: i32 = i32::MAX;
|
||||
|
||||
/// Api exposed on the `ExitRequested` event.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
|
||||
pub struct ExitRequestApi {
|
||||
tx: Sender<ExitRequestedEventAction>,
|
||||
code: Option<i32>,
|
||||
}
|
||||
|
||||
impl ExitRequestApi {
|
||||
/// Prevents the app from exiting.
|
||||
///
|
||||
/// **Note:** This is ignored when using [`AppHandle#method.restart`].
|
||||
pub fn prevent_exit(&self) {
|
||||
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
|
||||
if self.code != Some(RESTART_EXIT_CODE) {
|
||||
self.tx.send(ExitRequestedEventAction::Prevent).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +345,12 @@ impl<R: Runtime> AssetResolver<R> {
|
||||
pub struct AppHandle<R: Runtime> {
|
||||
pub(crate) runtime_handle: R::Handle,
|
||||
pub(crate) manager: Arc<AppManager<R>>,
|
||||
event_loop: Arc<Mutex<EventLoop>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EventLoop {
|
||||
main_thread_id: ThreadId,
|
||||
}
|
||||
|
||||
/// APIs specific to the wry runtime.
|
||||
@@ -428,6 +440,7 @@ impl<R: Runtime> Clone for AppHandle<R> {
|
||||
Self {
|
||||
runtime_handle: self.runtime_handle.clone(),
|
||||
manager: self.manager.clone(),
|
||||
event_loop: self.event_loop.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -522,10 +535,27 @@ impl<R: Runtime> AppHandle<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`] and [`RunEvent::Exit`]..
|
||||
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`](crate::RESTART_EXIT_CODE) and [`RunEvent::Exit`].
|
||||
///
|
||||
/// When this function is called on the main thread, we cannot guarantee the delivery of those events,
|
||||
/// so we skip them and directly restart the process.
|
||||
pub fn restart(&self) -> ! {
|
||||
if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
|
||||
if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {
|
||||
log::debug!("restart triggered on the main thread");
|
||||
self.cleanup_before_exit();
|
||||
self.manager.notify_event_loop_exit();
|
||||
} else {
|
||||
log::debug!("restart triggered from a separate thread");
|
||||
// we're running on a separate thread, so we must trigger the exit request and wait for it to finish
|
||||
match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {
|
||||
Ok(()) => {
|
||||
let _impede = self.manager.wait_for_event_loop_exit();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("failed to request exit: {e}");
|
||||
self.cleanup_before_exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::process::restart(&self.env());
|
||||
}
|
||||
@@ -1125,6 +1155,9 @@ impl<R: Runtime> App<R> {
|
||||
pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, mut callback: F) {
|
||||
let app_handle = self.handle().clone();
|
||||
let manager = self.manager.clone();
|
||||
|
||||
app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
|
||||
|
||||
self.runtime.take().unwrap().run(move |event| match event {
|
||||
RuntimeRunEvent::Ready => {
|
||||
if let Err(e) = setup(&mut self) {
|
||||
@@ -1137,6 +1170,7 @@ impl<R: Runtime> App<R> {
|
||||
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
|
||||
callback(&app_handle, event);
|
||||
app_handle.cleanup_before_exit();
|
||||
manager.notify_event_loop_exit();
|
||||
}
|
||||
_ => {
|
||||
let event = on_event_loop_event(&app_handle, event, &manager);
|
||||
@@ -1178,6 +1212,8 @@ impl<R: Runtime> App<R> {
|
||||
}
|
||||
}
|
||||
|
||||
app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
|
||||
|
||||
self.runtime.as_mut().unwrap().run_iteration(move |event| {
|
||||
let event = on_event_loop_event(&app_handle, event, &manager);
|
||||
callback(&app_handle, event);
|
||||
@@ -2016,6 +2052,9 @@ tauri::Builder::default()
|
||||
handle: AppHandle {
|
||||
runtime_handle,
|
||||
manager,
|
||||
event_loop: Arc::new(Mutex::new(EventLoop {
|
||||
main_thread_id: std::thread::current().id(),
|
||||
})),
|
||||
},
|
||||
ran_setup: false,
|
||||
};
|
||||
@@ -2207,7 +2246,7 @@ fn on_event_loop_event<R: Runtime>(
|
||||
RuntimeRunEvent::Exit => RunEvent::Exit,
|
||||
RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
|
||||
code,
|
||||
api: ExitRequestApi(tx),
|
||||
api: ExitRequestApi { tx, code },
|
||||
},
|
||||
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
|
||||
label,
|
||||
|
||||
@@ -214,7 +214,7 @@ use self::manager::EmitPayload;
|
||||
pub use {
|
||||
self::app::{
|
||||
App, AppHandle, AssetResolver, Builder, CloseRequestApi, ExitRequestApi, RunEvent,
|
||||
UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent,
|
||||
UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent, RESTART_EXIT_CODE,
|
||||
},
|
||||
self::manager::Asset,
|
||||
self::runtime::{
|
||||
|
||||
@@ -221,6 +221,13 @@ pub struct AppManager<R: Runtime> {
|
||||
pub(crate) invoke_key: String,
|
||||
|
||||
pub(crate) channel_interceptor: Option<ChannelInterceptor<R>>,
|
||||
|
||||
// the value is set to true when the event loop is already exited
|
||||
event_loop_exit_mutex: Mutex<bool>,
|
||||
event_loop_exit_condvar: std::sync::Condvar,
|
||||
// number of threads that request to NOT exit process
|
||||
impede_exit_count: Mutex<usize>,
|
||||
impede_exit_condvar: std::sync::Condvar,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for AppManager<R> {
|
||||
@@ -320,6 +327,10 @@ impl<R: Runtime> AppManager<R> {
|
||||
pattern: Arc::new(context.pattern),
|
||||
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
|
||||
resources_table: Arc::default(),
|
||||
event_loop_exit_mutex: Mutex::new(false),
|
||||
event_loop_exit_condvar: std::sync::Condvar::new(),
|
||||
impede_exit_count: Mutex::new(0),
|
||||
impede_exit_condvar: std::sync::Condvar::new(),
|
||||
invoke_key,
|
||||
channel_interceptor,
|
||||
}
|
||||
@@ -693,6 +704,55 @@ impl<R: Runtime> AppManager<R> {
|
||||
pub(crate) fn invoke_key(&self) -> &str {
|
||||
&self.invoke_key
|
||||
}
|
||||
|
||||
pub(crate) fn notify_event_loop_exit(&self) {
|
||||
let mut exit = self.event_loop_exit_mutex.lock().unwrap();
|
||||
*exit = true;
|
||||
self.event_loop_exit_condvar.notify_all();
|
||||
drop(exit);
|
||||
|
||||
self.wait_impede();
|
||||
}
|
||||
|
||||
pub(crate) fn wait_for_event_loop_exit(self: &Arc<Self>) -> ImpedeScope<R> {
|
||||
let impede = self.impede_quit();
|
||||
let mut exit = self.event_loop_exit_mutex.lock().unwrap();
|
||||
while !*exit {
|
||||
exit = self.event_loop_exit_condvar.wait(exit).unwrap();
|
||||
}
|
||||
impede
|
||||
}
|
||||
|
||||
/// When the main loop exits, most runtime would exit the process, so we need to impede the exit
|
||||
/// if we're going to restart the application.
|
||||
fn impede_quit(self: &Arc<Self>) -> ImpedeScope<R> {
|
||||
let mut pend_exit_threads = self.impede_exit_count.lock().unwrap();
|
||||
*pend_exit_threads += 1;
|
||||
ImpedeScope {
|
||||
app_manager: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_impede(&self) {
|
||||
let mut pend_exit_threads = self.impede_exit_count.lock().unwrap();
|
||||
while *pend_exit_threads > 0 {
|
||||
pend_exit_threads = self.impede_exit_condvar.wait(pend_exit_threads).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImpedeScope<R: Runtime> {
|
||||
app_manager: Arc<AppManager<R>>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Drop for ImpedeScope<R> {
|
||||
fn drop(&mut self) {
|
||||
let mut pend_exit_threads = self.app_manager.impede_exit_count.lock().unwrap();
|
||||
*pend_exit_threads -= 1;
|
||||
if *pend_exit_threads == 0 {
|
||||
self.app_manager.impede_exit_condvar.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
|
||||
Reference in New Issue
Block a user