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
This commit is contained in:
Marijn Suijten
2025-03-12 14:43:02 +01:00
committed by Robert Bragg
parent 1652ebb229
commit a97cf1ceae
+11 -5
View File
@@ -447,7 +447,9 @@ impl WaitableNativeActivityState {
guard.pending_input_queue = input_queue; guard.pending_input_queue = input_queue;
guard.write_cmd(AppCmd::InputQueueChanged); 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 = self.cond.wait(guard).unwrap();
} }
guard.pending_input_queue = ptr::null_mut(); guard.pending_input_queue = ptr::null_mut();
@@ -468,7 +470,9 @@ impl WaitableNativeActivityState {
if guard.pending_window.is_some() { if guard.pending_window.is_some() {
guard.write_cmd(AppCmd::InitWindow); 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 = self.cond.wait(guard).unwrap();
} }
guard.pending_window = None; guard.pending_window = None;
@@ -492,7 +496,7 @@ impl WaitableNativeActivityState {
}; };
guard.write_cmd(cmd); 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(); guard = self.cond.wait(guard).unwrap();
} }
} }
@@ -505,7 +509,7 @@ impl WaitableNativeActivityState {
// this to be None // this to be None
debug_assert!(!guard.app_has_saved_state, "SaveState request clash"); debug_assert!(!guard.app_has_saved_state, "SaveState request clash");
guard.write_cmd(AppCmd::SaveState); 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 = self.cond.wait(guard).unwrap();
} }
guard.app_has_saved_state = false; guard.app_has_saved_state = false;
@@ -560,7 +564,9 @@ impl WaitableNativeActivityState {
pub fn notify_main_thread_stopped_running(&self) { pub fn notify_main_thread_stopped_running(&self) {
let mut guard = self.mutex.lock().unwrap(); let mut guard = self.mutex.lock().unwrap();
guard.thread_state = NativeThreadState::Stopped; 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( pub unsafe fn pre_exec_cmd(