Skip to content

Commit b05f82d

Browse files
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>
1 parent 51bcafe commit b05f82d

File tree

4 files changed

+111
-7
lines changed

4 files changed

+111
-7
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:bug
3+
---
4+
5+
`AppHandle::restart()` now waits for `RunEvent::Exit` to be delivered before restarting the application.

crates/tauri/src/app.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ use std::{
4242
borrow::Cow,
4343
collections::HashMap,
4444
fmt,
45-
sync::{mpsc::Sender, Arc, MutexGuard},
45+
sync::{mpsc::Sender, Arc, Mutex, MutexGuard},
46+
thread::ThreadId,
4647
};
4748

4849
use crate::{event::EventId, runtime::RuntimeHandle, Event, EventTarget};
@@ -73,14 +74,19 @@ pub const RESTART_EXIT_CODE: i32 = i32::MAX;
7374

7475
/// Api exposed on the `ExitRequested` event.
7576
#[derive(Debug, Clone)]
76-
pub struct ExitRequestApi(Sender<ExitRequestedEventAction>);
77+
pub struct ExitRequestApi {
78+
tx: Sender<ExitRequestedEventAction>,
79+
code: Option<i32>,
80+
}
7781

7882
impl ExitRequestApi {
7983
/// Prevents the app from exiting.
8084
///
8185
/// **Note:** This is ignored when using [`AppHandle#method.restart`].
8286
pub fn prevent_exit(&self) {
83-
self.0.send(ExitRequestedEventAction::Prevent).unwrap();
87+
if self.code != Some(RESTART_EXIT_CODE) {
88+
self.tx.send(ExitRequestedEventAction::Prevent).unwrap();
89+
}
8490
}
8591
}
8692

@@ -339,6 +345,12 @@ impl<R: Runtime> AssetResolver<R> {
339345
pub struct AppHandle<R: Runtime> {
340346
pub(crate) runtime_handle: R::Handle,
341347
pub(crate) manager: Arc<AppManager<R>>,
348+
event_loop: Arc<Mutex<EventLoop>>,
349+
}
350+
351+
#[derive(Debug)]
352+
struct EventLoop {
353+
main_thread_id: ThreadId,
342354
}
343355

344356
/// APIs specific to the wry runtime.
@@ -428,6 +440,7 @@ impl<R: Runtime> Clone for AppHandle<R> {
428440
Self {
429441
runtime_handle: self.runtime_handle.clone(),
430442
manager: self.manager.clone(),
443+
event_loop: self.event_loop.clone(),
431444
}
432445
}
433446
}
@@ -522,10 +535,27 @@ impl<R: Runtime> AppHandle<R> {
522535
}
523536
}
524537

525-
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`] and [`RunEvent::Exit`]..
538+
/// Restarts the app by triggering [`RunEvent::ExitRequested`] with code [`RESTART_EXIT_CODE`](crate::RESTART_EXIT_CODE) and [`RunEvent::Exit`].
539+
///
540+
/// When this function is called on the main thread, we cannot guarantee the delivery of those events,
541+
/// so we skip them and directly restart the process.
526542
pub fn restart(&self) -> ! {
527-
if self.runtime_handle.request_exit(RESTART_EXIT_CODE).is_err() {
543+
if self.event_loop.lock().unwrap().main_thread_id == std::thread::current().id() {
544+
log::debug!("restart triggered on the main thread");
528545
self.cleanup_before_exit();
546+
self.manager.notify_event_loop_exit();
547+
} else {
548+
log::debug!("restart triggered from a separate thread");
549+
// we're running on a separate thread, so we must trigger the exit request and wait for it to finish
550+
match self.runtime_handle.request_exit(RESTART_EXIT_CODE) {
551+
Ok(()) => {
552+
let _impede = self.manager.wait_for_event_loop_exit();
553+
}
554+
Err(e) => {
555+
log::error!("failed to request exit: {e}");
556+
self.cleanup_before_exit();
557+
}
558+
}
529559
}
530560
crate::process::restart(&self.env());
531561
}
@@ -1125,6 +1155,9 @@ impl<R: Runtime> App<R> {
11251155
pub fn run<F: FnMut(&AppHandle<R>, RunEvent) + 'static>(mut self, mut callback: F) {
11261156
let app_handle = self.handle().clone();
11271157
let manager = self.manager.clone();
1158+
1159+
app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1160+
11281161
self.runtime.take().unwrap().run(move |event| match event {
11291162
RuntimeRunEvent::Ready => {
11301163
if let Err(e) = setup(&mut self) {
@@ -1137,6 +1170,7 @@ impl<R: Runtime> App<R> {
11371170
let event = on_event_loop_event(&app_handle, RuntimeRunEvent::Exit, &manager);
11381171
callback(&app_handle, event);
11391172
app_handle.cleanup_before_exit();
1173+
manager.notify_event_loop_exit();
11401174
}
11411175
_ => {
11421176
let event = on_event_loop_event(&app_handle, event, &manager);
@@ -1178,6 +1212,8 @@ impl<R: Runtime> App<R> {
11781212
}
11791213
}
11801214

1215+
app_handle.event_loop.lock().unwrap().main_thread_id = std::thread::current().id();
1216+
11811217
self.runtime.as_mut().unwrap().run_iteration(move |event| {
11821218
let event = on_event_loop_event(&app_handle, event, &manager);
11831219
callback(&app_handle, event);
@@ -2016,6 +2052,9 @@ tauri::Builder::default()
20162052
handle: AppHandle {
20172053
runtime_handle,
20182054
manager,
2055+
event_loop: Arc::new(Mutex::new(EventLoop {
2056+
main_thread_id: std::thread::current().id(),
2057+
})),
20192058
},
20202059
ran_setup: false,
20212060
};
@@ -2207,7 +2246,7 @@ fn on_event_loop_event<R: Runtime>(
22072246
RuntimeRunEvent::Exit => RunEvent::Exit,
22082247
RuntimeRunEvent::ExitRequested { code, tx } => RunEvent::ExitRequested {
22092248
code,
2210-
api: ExitRequestApi(tx),
2249+
api: ExitRequestApi { tx, code },
22112250
},
22122251
RuntimeRunEvent::WindowEvent { label, event } => RunEvent::WindowEvent {
22132252
label,

crates/tauri/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ use self::manager::EmitPayload;
214214
pub use {
215215
self::app::{
216216
App, AppHandle, AssetResolver, Builder, CloseRequestApi, ExitRequestApi, RunEvent,
217-
UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent,
217+
UriSchemeContext, UriSchemeResponder, WebviewEvent, WindowEvent, RESTART_EXIT_CODE,
218218
},
219219
self::manager::Asset,
220220
self::runtime::{

crates/tauri/src/manager/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ pub struct AppManager<R: Runtime> {
221221
pub(crate) invoke_key: String,
222222

223223
pub(crate) channel_interceptor: Option<ChannelInterceptor<R>>,
224+
225+
// the value is set to true when the event loop is already exited
226+
event_loop_exit_mutex: Mutex<bool>,
227+
event_loop_exit_condvar: std::sync::Condvar,
228+
// number of threads that request to NOT exit process
229+
impede_exit_count: Mutex<usize>,
230+
impede_exit_condvar: std::sync::Condvar,
224231
}
225232

226233
impl<R: Runtime> fmt::Debug for AppManager<R> {
@@ -320,6 +327,10 @@ impl<R: Runtime> AppManager<R> {
320327
pattern: Arc::new(context.pattern),
321328
plugin_global_api_scripts: Arc::new(context.plugin_global_api_scripts),
322329
resources_table: Arc::default(),
330+
event_loop_exit_mutex: Mutex::new(false),
331+
event_loop_exit_condvar: std::sync::Condvar::new(),
332+
impede_exit_count: Mutex::new(0),
333+
impede_exit_condvar: std::sync::Condvar::new(),
323334
invoke_key,
324335
channel_interceptor,
325336
}
@@ -693,6 +704,55 @@ impl<R: Runtime> AppManager<R> {
693704
pub(crate) fn invoke_key(&self) -> &str {
694705
&self.invoke_key
695706
}
707+
708+
pub(crate) fn notify_event_loop_exit(&self) {
709+
let mut exit = self.event_loop_exit_mutex.lock().unwrap();
710+
*exit = true;
711+
self.event_loop_exit_condvar.notify_all();
712+
drop(exit);
713+
714+
self.wait_impede();
715+
}
716+
717+
pub(crate) fn wait_for_event_loop_exit(self: &Arc<Self>) -> ImpedeScope<R> {
718+
let impede = self.impede_quit();
719+
let mut exit = self.event_loop_exit_mutex.lock().unwrap();
720+
while !*exit {
721+
exit = self.event_loop_exit_condvar.wait(exit).unwrap();
722+
}
723+
impede
724+
}
725+
726+
/// When the main loop exits, most runtime would exit the process, so we need to impede the exit
727+
/// if we're going to restart the application.
728+
fn impede_quit(self: &Arc<Self>) -> ImpedeScope<R> {
729+
let mut pend_exit_threads = self.impede_exit_count.lock().unwrap();
730+
*pend_exit_threads += 1;
731+
ImpedeScope {
732+
app_manager: self.clone(),
733+
}
734+
}
735+
736+
fn wait_impede(&self) {
737+
let mut pend_exit_threads = self.impede_exit_count.lock().unwrap();
738+
while *pend_exit_threads > 0 {
739+
pend_exit_threads = self.impede_exit_condvar.wait(pend_exit_threads).unwrap();
740+
}
741+
}
742+
}
743+
744+
pub struct ImpedeScope<R: Runtime> {
745+
app_manager: Arc<AppManager<R>>,
746+
}
747+
748+
impl<R: Runtime> Drop for ImpedeScope<R> {
749+
fn drop(&mut self) {
750+
let mut pend_exit_threads = self.app_manager.impede_exit_count.lock().unwrap();
751+
*pend_exit_threads -= 1;
752+
if *pend_exit_threads == 0 {
753+
self.app_manager.impede_exit_condvar.notify_all();
754+
}
755+
}
696756
}
697757

698758
#[cfg(desktop)]

0 commit comments

Comments
 (0)