Commit e4e0a12901321a1dd6f949fa0a385684c2a17553

Sam Lantinga 2023-06-12T15:26:34

Reduce the chance of destroying the joystick mutex while it's in use Fixes https://github.com/libsdl-org/SDL/issues/7811 (cherry picked from commit 6390165fd4c193631d6780758a4aeec0d02b90eb)

diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index c3120e7..02d7edd 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -114,6 +114,7 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = {
 static
 #endif
 SDL_mutex *SDL_joystick_lock = NULL; /* This needs to support recursive locks */
+static SDL_atomic_t SDL_joystick_lock_pending;
 static int SDL_joysticks_locked;
 static SDL_bool SDL_joysticks_initialized;
 static SDL_bool SDL_joysticks_quitting = SDL_FALSE;
@@ -143,23 +144,35 @@ SDL_bool SDL_JoysticksQuitting(void)
 
 void SDL_LockJoysticks(void)
 {
+    SDL_AtomicIncRef(&SDL_joystick_lock_pending);
     SDL_LockMutex(SDL_joystick_lock);
+    SDL_AtomicDecRef(&SDL_joystick_lock_pending);
 
     ++SDL_joysticks_locked;
 }
 
 void SDL_UnlockJoysticks(void)
 {
+    SDL_mutex *joystick_lock = SDL_joystick_lock;
+    SDL_bool last_unlock = SDL_FALSE;
+
     --SDL_joysticks_locked;
 
-    SDL_UnlockMutex(SDL_joystick_lock);
+    if (!SDL_joysticks_initialized) {
+        if (!SDL_joysticks_locked && SDL_AtomicGet(&SDL_joystick_lock_pending) == 0) {
+            /* NOTE: There's a small window here where another thread could lock the mutex */
+            SDL_joystick_lock = NULL;
+            last_unlock = SDL_TRUE;
+        }
+    }
+
+    SDL_UnlockMutex(joystick_lock);
 
     /* The last unlock after joysticks are uninitialized will cleanup the mutex,
      * allowing applications to lock joysticks while reinitializing the system.
      */
-    if (SDL_joystick_lock && !SDL_joysticks_locked && !SDL_joysticks_initialized) {
-        SDL_DestroyMutex(SDL_joystick_lock);
-        SDL_joystick_lock = NULL;
+    if (last_unlock) {
+        SDL_DestroyMutex(joystick_lock);
     }
 }