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)