SDL: Fixed rare crash trying to interrupt SDL_WaitEvent() (4177e)

From 4177e06c099925ffb567d5adab6c415df67c6d88 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 19 May 2025 13:53:33 -0700
Subject: [PATCH] Fixed rare crash trying to interrupt SDL_WaitEvent()

Fixes https://github.com/libsdl-org/SDL/issues/12797

(cherry picked from commit 992e4c59bdf03dbec8b1689332ee697623cf6ad2)
(cherry picked from commit 33eb167da8487e543cba4204de856da65fa9ca51)
---
 src/events/SDL_events.c              | 33 +++++++---------------------
 src/video/SDL_sysvideo.h             |  3 +--
 src/video/SDL_video.c                |  4 +---
 src/video/cocoa/SDL_cocoavideo.m     |  4 ----
 src/video/wayland/SDL_waylandvideo.c |  4 ----
 src/video/windows/SDL_windowsvideo.c |  4 ----
 src/video/x11/SDL_x11video.c         |  5 -----
 7 files changed, 10 insertions(+), 47 deletions(-)

diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 3dcd9a8f66f57..271e2504b9491 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -738,21 +738,17 @@ static void SDL_CutEvent(SDL_EventEntry *entry)
 
 static int SDL_SendWakeupEvent(void)
 {
+    SDL_Window *wakeup_window;
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
     if (_this == NULL || !_this->SendWakeupEvent) {
         return 0;
     }
 
-    SDL_LockMutex(_this->wakeup_lock);
-    {
-        if (_this->wakeup_window) {
-            _this->SendWakeupEvent(_this, _this->wakeup_window);
-
-            /* No more wakeup events needed until we enter a new wait */
-            _this->wakeup_window = NULL;
-        }
+    /* We only want to do this once while waiting for an event, so set it to NULL atomically here */
+    wakeup_window = (SDL_Window *)SDL_AtomicSetPtr(&_this->wakeup_window, NULL);
+    if (wakeup_window) {
+        _this->SendWakeupEvent(_this, wakeup_window);
     }
-    SDL_UnlockMutex(_this->wakeup_lock);
 
     return 0;
 }
@@ -1009,18 +1005,7 @@ static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Eve
         int status;
         SDL_PumpEventsInternal(SDL_TRUE);
 
-        SDL_LockMutex(_this->wakeup_lock);
-        {
-            status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
-            /* If status == 0 we are going to block so wakeup will be needed. */
-            if (status == 0) {
-                _this->wakeup_window = wakeup_window;
-            } else {
-                _this->wakeup_window = NULL;
-            }
-        }
-        SDL_UnlockMutex(_this->wakeup_lock);
-
+        status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
         if (status < 0) {
             /* Got an error: return */
             break;
@@ -1033,8 +1018,6 @@ static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Eve
         if (timeout > 0) {
             Uint32 elapsed = SDL_GetTicks() - start;
             if (elapsed >= (Uint32)timeout) {
-                /* Set wakeup_window to NULL without holding the lock. */
-                _this->wakeup_window = NULL;
                 return 0;
             }
             loop_timeout = (int)((Uint32)timeout - elapsed);
@@ -1049,9 +1032,9 @@ static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Eve
             }
         }
 
+        SDL_AtomicSetPtr(&_this->wakeup_window, wakeup_window);
         status = _this->WaitEventTimeout(_this, loop_timeout);
-        /* Set wakeup_window to NULL without holding the lock. */
-        _this->wakeup_window = NULL;
+        SDL_AtomicSetPtr(&_this->wakeup_window, NULL);
         if (status == 0 && poll_interval != SDL_MAX_SINT16 && loop_timeout == poll_interval) {
             /* We may have woken up to poll. Try again */
             continue;
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 22bca8a222c92..faf598be388b8 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -355,8 +355,7 @@ struct SDL_VideoDevice
     SDL_bool checked_texture_framebuffer;
     SDL_bool is_dummy;
     SDL_bool suspend_screensaver;
-    SDL_Window *wakeup_window;
-    SDL_mutex *wakeup_lock; /* Initialized only if WaitEventTimeout/SendWakeupEvent are supported */
+    void *wakeup_window;
     int num_displays;
     SDL_VideoDisplay *displays;
     SDL_Window *windows;
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 01023753c6e49..2f6c631d12f40 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -3368,9 +3368,7 @@ void SDL_DestroyWindow(SDL_Window *window)
         _this->current_glwin = NULL;
     }
 
-    if (_this->wakeup_window == window) {
-        _this->wakeup_window = NULL;
-    }
+    SDL_AtomicCASPtr(&_this->wakeup_window, window, NULL);
 
     /* Now invalidate magic */
     window->magic = NULL;
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index 811e85dbb9703..4ca68ba836fa6 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -48,9 +48,6 @@ @implementation SDL_VideoData
 static void Cocoa_DeleteDevice(SDL_VideoDevice * device)
 { @autoreleasepool
 {
-    if (device->wakeup_lock) {
-        SDL_DestroyMutex(device->wakeup_lock);
-    }
     CFBridgingRelease(device->driverdata);
     SDL_free(device);
 }}
@@ -76,7 +73,6 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice * device)
         return NULL;
     }
     device->driverdata = (void *)CFBridgingRetain(data);
-    device->wakeup_lock = SDL_CreateMutex();
 
     /* Set the function pointers */
     device->VideoInit = Cocoa_VideoInit;
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 1d525be496542..0549520d61acd 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -162,9 +162,6 @@ static void Wayland_DeleteDevice(SDL_VideoDevice *device)
         WAYLAND_wl_display_flush(data->display);
         WAYLAND_wl_display_disconnect(data->display);
     }
-    if (device->wakeup_lock) {
-        SDL_DestroyMutex(device->wakeup_lock);
-    }
     SDL_free(data);
     SDL_free(device);
     SDL_WAYLAND_UnloadSymbols();
@@ -233,7 +230,6 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
     }
 
     device->driverdata = data;
-    device->wakeup_lock = SDL_CreateMutex();
 
     /* Set the function pointers */
     device->VideoInit = Wayland_VideoInit;
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 7c0265805485b..6c71bdcb60b40 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -93,9 +93,6 @@ static void WIN_DeleteDevice(SDL_VideoDevice *device)
         SDL_UnloadObject(data->shcoreDLL);
     }
 #endif
-    if (device->wakeup_lock) {
-        SDL_DestroyMutex(device->wakeup_lock);
-    }
     SDL_free(device->driverdata);
     SDL_free(device);
 }
@@ -120,7 +117,6 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
         return NULL;
     }
     device->driverdata = data;
-    device->wakeup_lock = SDL_CreateMutex();
 
 #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__)
     data->userDLL = SDL_LoadObject("USER32.DLL");
diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c
index 9023add317d7c..b43da511681dd 100644
--- a/src/video/x11/SDL_x11video.c
+++ b/src/video/x11/SDL_x11video.c
@@ -108,9 +108,6 @@ static void X11_DeleteDevice(SDL_VideoDevice *device)
         X11_XCloseDisplay(data->request_display);
     }
     SDL_free(data->windowlist);
-    if (device->wakeup_lock) {
-        SDL_DestroyMutex(device->wakeup_lock);
-    }
     SDL_free(device->driverdata);
     SDL_free(device);
 
@@ -204,8 +201,6 @@ static SDL_VideoDevice *X11_CreateDevice(void)
         return NULL;
     }
 
-    device->wakeup_lock = SDL_CreateMutex();
-
 #ifdef X11_DEBUG
     X11_XSynchronize(data->display, True);
 #endif