diff --git a/.changes/fix-window-getter-deadlock.md b/.changes/fix-window-getter-deadlock.md new file mode 100644 index 000000000..bf4c137a5 --- /dev/null +++ b/.changes/fix-window-getter-deadlock.md @@ -0,0 +1,6 @@ +--- +"tauri-runtime-wry": patch +"tauri": patch +--- + +Panic on window getters usage on the main thread when the event loop is not running and document it. diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index ea9a2e97c..6a37cba7b 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -52,14 +52,13 @@ use std::{ convert::TryFrom, fs::read, sync::{ + atomic::{AtomicBool, Ordering}, mpsc::{channel, Sender}, Arc, Mutex, MutexGuard, }, + thread::{current as current_thread, ThreadId}, }; -#[cfg(feature = "menu")] -use std::sync::atomic::{AtomicBool, Ordering}; - #[cfg(any(feature = "menu", feature = "system-tray"))] mod menu; #[cfg(any(feature = "menu", feature = "system-tray"))] @@ -523,6 +522,8 @@ pub(crate) enum Message { #[derive(Clone)] struct DispatcherContext { + main_thread_id: ThreadId, + is_event_loop_running: Arc, proxy: EventLoopProxy, window_event_listeners: WindowEventListeners, #[cfg(feature = "menu")] @@ -538,6 +539,11 @@ pub struct WryDispatcher { macro_rules! dispatcher_getter { ($self: ident, $message: expr) => {{ + if current_thread().id() == $self.context.main_thread_id + && !$self.context.is_event_loop_running.load(Ordering::Relaxed) + { + panic!("This API cannot be called when the event loop is not running"); + } let (tx, rx) = channel(); $self .context @@ -954,6 +960,8 @@ struct WebviewWrapper { /// A Tauri [`Runtime`] wrapper around wry. pub struct Wry { + main_thread_id: ThreadId, + is_event_loop_running: Arc, event_loop: EventLoop, webviews: Arc>>, window_event_listeners: WindowEventListeners, @@ -1018,6 +1026,8 @@ impl Runtime for Wry { fn new() -> Result { let event_loop = EventLoop::::with_user_event(); Ok(Self { + main_thread_id: current_thread().id(), + is_event_loop_running: Default::default(), event_loop, webviews: Default::default(), window_event_listeners: Default::default(), @@ -1031,6 +1041,8 @@ impl Runtime for Wry { fn handle(&self) -> Self::Handle { WryHandle { dispatcher_context: DispatcherContext { + main_thread_id: self.main_thread_id, + is_event_loop_running: self.is_event_loop_running.clone(), proxy: self.event_loop.create_proxy(), window_event_listeners: self.window_event_listeners.clone(), #[cfg(feature = "menu")] @@ -1048,6 +1060,8 @@ impl Runtime for Wry { let webview = create_webview( &self.event_loop, DispatcherContext { + main_thread_id: self.main_thread_id, + is_event_loop_running: self.is_event_loop_running.clone(), proxy: proxy.clone(), window_event_listeners: self.window_event_listeners.clone(), #[cfg(feature = "menu")] @@ -1059,6 +1073,8 @@ impl Runtime for Wry { let dispatcher = WryDispatcher { window_id: webview.inner.window().id(), context: DispatcherContext { + main_thread_id: self.main_thread_id, + is_event_loop_running: self.is_event_loop_running.clone(), proxy, window_event_listeners: self.window_event_listeners.clone(), #[cfg(feature = "menu")] @@ -1125,6 +1141,7 @@ impl Runtime for Wry { let mut iteration = RunIteration::default(); + self.is_event_loop_running.store(true, Ordering::Relaxed); self .event_loop .run_return(|event, event_loop, control_flow| { @@ -1146,11 +1163,13 @@ impl Runtime for Wry { }, ); }); + self.is_event_loop_running.store(false, Ordering::Relaxed); iteration } fn run(self, callback: F) { + self.is_event_loop_running.store(true, Ordering::Relaxed); let webviews = self.webviews.clone(); let window_event_listeners = self.window_event_listeners.clone(); #[cfg(feature = "menu")] diff --git a/core/tauri/src/window.rs b/core/tauri/src/window.rs index 527da1211..28cd9cff6 100644 --- a/core/tauri/src/window.rs +++ b/core/tauri/src/window.rs @@ -308,16 +308,31 @@ impl Window

{ } /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn scale_factor(&self) -> crate::Result { self.window.dispatcher.scale_factor().map_err(Into::into) } /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn inner_position(&self) -> crate::Result> { self.window.dispatcher.inner_position().map_err(Into::into) } /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn outer_position(&self) -> crate::Result> { self.window.dispatcher.outer_position().map_err(Into::into) } @@ -325,6 +340,11 @@ impl Window

{ /// Returns the physical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn inner_size(&self) -> crate::Result> { self.window.dispatcher.inner_size().map_err(Into::into) } @@ -332,31 +352,61 @@ impl Window

{ /// Returns the physical size of the entire window. /// /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn outer_size(&self) -> crate::Result> { self.window.dispatcher.outer_size().map_err(Into::into) } /// Gets the window's current fullscreen state. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_fullscreen(&self) -> crate::Result { self.window.dispatcher.is_fullscreen().map_err(Into::into) } /// Gets the window's current maximized state. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_maximized(&self) -> crate::Result { self.window.dispatcher.is_maximized().map_err(Into::into) } /// Gets the window’s current decoration state. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_decorated(&self) -> crate::Result { self.window.dispatcher.is_decorated().map_err(Into::into) } /// Gets the window’s current resizable state. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_resizable(&self) -> crate::Result { self.window.dispatcher.is_resizable().map_err(Into::into) } /// Gets the window's current vibility state. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_visible(&self) -> crate::Result { self.window.dispatcher.is_visible().map_err(Into::into) } @@ -364,6 +414,15 @@ impl Window

{ /// Returns the monitor on which the window currently resides. /// /// Returns None if current monitor can't be detected. + /// + /// ## Platform-specific + /// + /// - **Linux:** Unsupported + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn current_monitor(&self) -> crate::Result> { self .window @@ -376,6 +435,15 @@ impl Window

{ /// Returns the primary monitor of the system. /// /// Returns None if it can't identify any monitor as a primary one. + /// + /// ## Platform-specific + /// + /// - **Linux:** Unsupported + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn primary_monitor(&self) -> crate::Result> { self .window @@ -386,6 +454,15 @@ impl Window

{ } /// Returns the list of all the monitors available on the system. + /// + /// ## Platform-specific + /// + /// - **Linux:** Unsupported + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn available_monitors(&self) -> crate::Result> { self .window @@ -396,6 +473,11 @@ impl Window

{ } /// Returns the native handle that is used by this window. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. #[cfg(windows)] pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> { self.window.dispatcher.hwnd().map_err(Into::into) diff --git a/core/tauri/src/window/menu.rs b/core/tauri/src/window/menu.rs index 13c9666dd..ae8d1fb1a 100644 --- a/core/tauri/src/window/menu.rs +++ b/core/tauri/src/window/menu.rs @@ -82,11 +82,21 @@ impl MenuHandle

{ } /// Whether the menu is visible or not. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn is_visible(&self) -> crate::Result { self.dispatcher.is_menu_visible().map_err(Into::into) } /// Toggles the menu visibility. + /// + /// # Panics + /// + /// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure. + /// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic. pub fn toggle(&self) -> crate::Result<()> { if self.is_visible()? { self.hide()