From 5a82603ce05d187fd0e9817771a4739bedb8f7f4 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 12 Mar 2025 14:43:02 +0100 Subject: [PATCH] native_activity: Only wait for state to update while main thread is running We see that some Android callbacks like `onStart()` deadlock, specifically when returning out of the main thread before running any event loop (but likely also whenever terminating the event loop), because they don't check if the thread is still even running and are otherwise guaranteed receive an `activity_state` update or other state change to unblock themselves. This is a followup to [#94] which only concerned itself with a deadlock caused by a destructor not running because that very object was kept alive to poll on the `destroyed` field that destructor was supposed to set, but its new `thread_state` can be reused to disable these condvar waits when the "sending" thread has disappeared. Separately, that PR mentions `Activity` recreates because of configuration changes which isn't supported anyway because `Activity` is still wrongly assumed to be a global singleton. [#94]: https://togithub.com/rust-mobile/android-activity/pull/94 --- android-activity/src/native_activity/glue.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index dab3e29..82672a5 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -447,7 +447,9 @@ impl WaitableNativeActivityState { guard.pending_input_queue = input_queue; guard.write_cmd(AppCmd::InputQueueChanged); - while guard.input_queue != guard.pending_input_queue { + while guard.thread_state == NativeThreadState::Running + && guard.input_queue != guard.pending_input_queue + { guard = self.cond.wait(guard).unwrap(); } guard.pending_input_queue = ptr::null_mut(); @@ -468,7 +470,9 @@ impl WaitableNativeActivityState { if guard.pending_window.is_some() { guard.write_cmd(AppCmd::InitWindow); } - while guard.window != guard.pending_window { + while guard.thread_state == NativeThreadState::Running + && guard.window != guard.pending_window + { guard = self.cond.wait(guard).unwrap(); } guard.pending_window = None; @@ -492,7 +496,7 @@ impl WaitableNativeActivityState { }; guard.write_cmd(cmd); - while guard.activity_state != state { + while guard.thread_state == NativeThreadState::Running && guard.activity_state != state { guard = self.cond.wait(guard).unwrap(); } } @@ -505,7 +509,7 @@ impl WaitableNativeActivityState { // this to be None debug_assert!(!guard.app_has_saved_state, "SaveState request clash"); guard.write_cmd(AppCmd::SaveState); - while !guard.app_has_saved_state { + while guard.thread_state == NativeThreadState::Running && !guard.app_has_saved_state { guard = self.cond.wait(guard).unwrap(); } guard.app_has_saved_state = false; @@ -560,7 +564,9 @@ impl WaitableNativeActivityState { pub fn notify_main_thread_stopped_running(&self) { let mut guard = self.mutex.lock().unwrap(); guard.thread_state = NativeThreadState::Stopped; - self.cond.notify_one(); + // Notify all waiters to unblock any Android callbacks that would otherwise be waiting + // indefinitely for the now-stopped (!) main thread. + self.cond.notify_all(); } pub unsafe fn pre_exec_cmd(