Skip to content

Commit ab3eb44

Browse files
authored
fix(core): deadlock on window getters, fixes #1893 (#1998)
* fix(core): deadlock on window getters, fixes #1893 * fix compilation without menu feature
1 parent 3f43bc6 commit ab3eb44

4 files changed

Lines changed: 120 additions & 3 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-runtime-wry": patch
3+
"tauri": patch
4+
---
5+
6+
Panic on window getters usage on the main thread when the event loop is not running and document it.

core/tauri-runtime-wry/src/lib.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,13 @@ use std::{
5252
convert::TryFrom,
5353
fs::read,
5454
sync::{
55+
atomic::{AtomicBool, Ordering},
5556
mpsc::{channel, Sender},
5657
Arc, Mutex, MutexGuard,
5758
},
59+
thread::{current as current_thread, ThreadId},
5860
};
5961

60-
#[cfg(feature = "menu")]
61-
use std::sync::atomic::{AtomicBool, Ordering};
62-
6362
#[cfg(any(feature = "menu", feature = "system-tray"))]
6463
mod menu;
6564
#[cfg(any(feature = "menu", feature = "system-tray"))]
@@ -523,6 +522,8 @@ pub(crate) enum Message {
523522

524523
#[derive(Clone)]
525524
struct DispatcherContext {
525+
main_thread_id: ThreadId,
526+
is_event_loop_running: Arc<AtomicBool>,
526527
proxy: EventLoopProxy<Message>,
527528
window_event_listeners: WindowEventListeners,
528529
#[cfg(feature = "menu")]
@@ -538,6 +539,11 @@ pub struct WryDispatcher {
538539

539540
macro_rules! dispatcher_getter {
540541
($self: ident, $message: expr) => {{
542+
if current_thread().id() == $self.context.main_thread_id
543+
&& !$self.context.is_event_loop_running.load(Ordering::Relaxed)
544+
{
545+
panic!("This API cannot be called when the event loop is not running");
546+
}
541547
let (tx, rx) = channel();
542548
$self
543549
.context
@@ -954,6 +960,8 @@ struct WebviewWrapper {
954960

955961
/// A Tauri [`Runtime`] wrapper around wry.
956962
pub struct Wry {
963+
main_thread_id: ThreadId,
964+
is_event_loop_running: Arc<AtomicBool>,
957965
event_loop: EventLoop<Message>,
958966
webviews: Arc<Mutex<HashMap<WindowId, WebviewWrapper>>>,
959967
window_event_listeners: WindowEventListeners,
@@ -1018,6 +1026,8 @@ impl Runtime for Wry {
10181026
fn new() -> Result<Self> {
10191027
let event_loop = EventLoop::<Message>::with_user_event();
10201028
Ok(Self {
1029+
main_thread_id: current_thread().id(),
1030+
is_event_loop_running: Default::default(),
10211031
event_loop,
10221032
webviews: Default::default(),
10231033
window_event_listeners: Default::default(),
@@ -1031,6 +1041,8 @@ impl Runtime for Wry {
10311041
fn handle(&self) -> Self::Handle {
10321042
WryHandle {
10331043
dispatcher_context: DispatcherContext {
1044+
main_thread_id: self.main_thread_id,
1045+
is_event_loop_running: self.is_event_loop_running.clone(),
10341046
proxy: self.event_loop.create_proxy(),
10351047
window_event_listeners: self.window_event_listeners.clone(),
10361048
#[cfg(feature = "menu")]
@@ -1048,6 +1060,8 @@ impl Runtime for Wry {
10481060
let webview = create_webview(
10491061
&self.event_loop,
10501062
DispatcherContext {
1063+
main_thread_id: self.main_thread_id,
1064+
is_event_loop_running: self.is_event_loop_running.clone(),
10511065
proxy: proxy.clone(),
10521066
window_event_listeners: self.window_event_listeners.clone(),
10531067
#[cfg(feature = "menu")]
@@ -1059,6 +1073,8 @@ impl Runtime for Wry {
10591073
let dispatcher = WryDispatcher {
10601074
window_id: webview.inner.window().id(),
10611075
context: DispatcherContext {
1076+
main_thread_id: self.main_thread_id,
1077+
is_event_loop_running: self.is_event_loop_running.clone(),
10621078
proxy,
10631079
window_event_listeners: self.window_event_listeners.clone(),
10641080
#[cfg(feature = "menu")]
@@ -1125,6 +1141,7 @@ impl Runtime for Wry {
11251141

11261142
let mut iteration = RunIteration::default();
11271143

1144+
self.is_event_loop_running.store(true, Ordering::Relaxed);
11281145
self
11291146
.event_loop
11301147
.run_return(|event, event_loop, control_flow| {
@@ -1146,11 +1163,13 @@ impl Runtime for Wry {
11461163
},
11471164
);
11481165
});
1166+
self.is_event_loop_running.store(false, Ordering::Relaxed);
11491167

11501168
iteration
11511169
}
11521170

11531171
fn run<F: Fn() + 'static>(self, callback: F) {
1172+
self.is_event_loop_running.store(true, Ordering::Relaxed);
11541173
let webviews = self.webviews.clone();
11551174
let window_event_listeners = self.window_event_listeners.clone();
11561175
#[cfg(feature = "menu")]

core/tauri/src/window.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,62 +308,121 @@ impl<P: Params> Window<P> {
308308
}
309309

310310
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
311+
///
312+
/// # Panics
313+
///
314+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
315+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
311316
pub fn scale_factor(&self) -> crate::Result<f64> {
312317
self.window.dispatcher.scale_factor().map_err(Into::into)
313318
}
314319

315320
/// 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.
321+
///
322+
/// # Panics
323+
///
324+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
325+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
316326
pub fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>> {
317327
self.window.dispatcher.inner_position().map_err(Into::into)
318328
}
319329

320330
/// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.
331+
///
332+
/// # Panics
333+
///
334+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
335+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
321336
pub fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>> {
322337
self.window.dispatcher.outer_position().map_err(Into::into)
323338
}
324339

325340
/// Returns the physical size of the window's client area.
326341
///
327342
/// The client area is the content of the window, excluding the title bar and borders.
343+
///
344+
/// # Panics
345+
///
346+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
347+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
328348
pub fn inner_size(&self) -> crate::Result<PhysicalSize<u32>> {
329349
self.window.dispatcher.inner_size().map_err(Into::into)
330350
}
331351

332352
/// Returns the physical size of the entire window.
333353
///
334354
/// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.
355+
///
356+
/// # Panics
357+
///
358+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
359+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
335360
pub fn outer_size(&self) -> crate::Result<PhysicalSize<u32>> {
336361
self.window.dispatcher.outer_size().map_err(Into::into)
337362
}
338363

339364
/// Gets the window's current fullscreen state.
365+
///
366+
/// # Panics
367+
///
368+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
369+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
340370
pub fn is_fullscreen(&self) -> crate::Result<bool> {
341371
self.window.dispatcher.is_fullscreen().map_err(Into::into)
342372
}
343373

344374
/// Gets the window's current maximized state.
375+
///
376+
/// # Panics
377+
///
378+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
379+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
345380
pub fn is_maximized(&self) -> crate::Result<bool> {
346381
self.window.dispatcher.is_maximized().map_err(Into::into)
347382
}
348383

349384
/// Gets the window’s current decoration state.
385+
///
386+
/// # Panics
387+
///
388+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
389+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
350390
pub fn is_decorated(&self) -> crate::Result<bool> {
351391
self.window.dispatcher.is_decorated().map_err(Into::into)
352392
}
353393

354394
/// Gets the window’s current resizable state.
395+
///
396+
/// # Panics
397+
///
398+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
399+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
355400
pub fn is_resizable(&self) -> crate::Result<bool> {
356401
self.window.dispatcher.is_resizable().map_err(Into::into)
357402
}
358403

359404
/// Gets the window's current vibility state.
405+
///
406+
/// # Panics
407+
///
408+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
409+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
360410
pub fn is_visible(&self) -> crate::Result<bool> {
361411
self.window.dispatcher.is_visible().map_err(Into::into)
362412
}
363413

364414
/// Returns the monitor on which the window currently resides.
365415
///
366416
/// Returns None if current monitor can't be detected.
417+
///
418+
/// ## Platform-specific
419+
///
420+
/// - **Linux:** Unsupported
421+
///
422+
/// # Panics
423+
///
424+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
425+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
367426
pub fn current_monitor(&self) -> crate::Result<Option<Monitor>> {
368427
self
369428
.window
@@ -376,6 +435,15 @@ impl<P: Params> Window<P> {
376435
/// Returns the primary monitor of the system.
377436
///
378437
/// Returns None if it can't identify any monitor as a primary one.
438+
///
439+
/// ## Platform-specific
440+
///
441+
/// - **Linux:** Unsupported
442+
///
443+
/// # Panics
444+
///
445+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
446+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
379447
pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {
380448
self
381449
.window
@@ -386,6 +454,15 @@ impl<P: Params> Window<P> {
386454
}
387455

388456
/// Returns the list of all the monitors available on the system.
457+
///
458+
/// ## Platform-specific
459+
///
460+
/// - **Linux:** Unsupported
461+
///
462+
/// # Panics
463+
///
464+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
465+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
389466
pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
390467
self
391468
.window
@@ -396,6 +473,11 @@ impl<P: Params> Window<P> {
396473
}
397474

398475
/// Returns the native handle that is used by this window.
476+
///
477+
/// # Panics
478+
///
479+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
480+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
399481
#[cfg(windows)]
400482
pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> {
401483
self.window.dispatcher.hwnd().map_err(Into::into)

core/tauri/src/window/menu.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,21 @@ impl<P: Params> MenuHandle<P> {
8282
}
8383

8484
/// Whether the menu is visible or not.
85+
///
86+
/// # Panics
87+
///
88+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
89+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
8590
pub fn is_visible(&self) -> crate::Result<bool> {
8691
self.dispatcher.is_menu_visible().map_err(Into::into)
8792
}
8893

8994
/// Toggles the menu visibility.
95+
///
96+
/// # Panics
97+
///
98+
/// Panics if the app is not running yet, usually when called on the [`setup`](crate::Builder#method.setup) closure.
99+
/// You can spawn a task to use the API using the [`async_runtime`](crate::async_runtime) to prevent the panic.
90100
pub fn toggle(&self) -> crate::Result<()> {
91101
if self.is_visible()? {
92102
self.hide()

0 commit comments

Comments
 (0)