SDL: android: fixed a possible joystick-related deadlock on application termination (9a1ba)

From 9a1ba9f81171862704bf9386edaf7144f7351d54 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 21 May 2026 12:20:03 -0700
Subject: [PATCH] android: fixed a possible joystick-related deadlock on
 application termination

(cherry picked from commit 6b4ae6846017b4afcde8f91c7d1bd2e2512c0ff8)
---
 src/video/android/SDL_androidevents.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c
index 4bdd717e121fe..605591b990f57 100644
--- a/src/video/android/SDL_androidevents.c
+++ b/src/video/android/SDL_androidevents.c
@@ -178,6 +178,26 @@ static void Android_OnDestroy(void)
 
 static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event)
 {
+    // Make sure we're holding the joystick lock before dispatching life cycle events
+    // This prevents deadlocks of this form:
+    //   1. SDL locks the event watch list to dispatch the lifecycle event
+    //   2. a joystick sensor event arrives, taking the joystick lock
+    //   3. the lifecycle event dispatch calls into the application event
+    //      watcher, which closes joysticks, trying to take the joystick lock
+    //   4. SDL delivers the sensor event, trying lock the event watch list
+    //   5. BOOM, deadlock!
+    //
+    // There are a few solutions to this, most of which involve unlocking the
+    // event watch list while delivering events or unlocking the joystick lock
+    // while delivering joystick events, both of which reduce performance and
+    // are extremely risky, so we'll do this, which is the least risky option.
+    //
+    // If you end up needing to wait for another thread that handles joystick
+    // events in your life cycle handling, then you can simply unlock joysticks,
+    // wait for that thread to complete, and then re-lock joysticks.
+
+    SDL_LockJoysticks();
+
     switch (event) {
     case SDL_ANDROID_LIFECYCLE_WAKE:
         // Nothing to do, just return
@@ -197,6 +217,8 @@ static void Android_HandleLifecycleEvent(SDL_AndroidLifecycleEvent event)
     default:
         break;
     }
+
+    SDL_UnlockJoysticks();
 }
 
 static Sint64 GetLifecycleEventTimeout(bool paused, Sint64 timeoutNS)