Commit 1fa154fda3b2a811e95478cd39572b7c306b5ae6

Sam Lantinga 2021-10-13T09:33:54

Fix weak enforcement of timeouts in SDL_WaitEventTimeout_Device. This will loop pumping events and waiting for a system event to come in. However not all system events will turn into an SDL event. It's not unusual for a Windows message to be some internal thing that SDL doesn't convert into a message. In that case the loop will simple circle but not exit. As long as such messages are coming in the loop will continue to run regardless of the timeout. When messages finally stop it'll still wait for the full timeout so you can have arbitrarily long delays. Instead do an absolute elapsed time check since the start of the wait. If that is exceeded during any iteration the routine exits as the timeout has elapsed.

diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 771c0a6..599ec76 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -786,8 +786,10 @@ SDL_PollEvent(SDL_Event * event)
 }
 
 static int
-SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event, int timeout)
+SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event, Uint32 start, int timeout)
 {
+	int loop_timeout = timeout;
+
     for (;;) {
         /* Pump events on entry and each time we wake to ensure:
            a) All pending events are batch processed after waking up from a wait
@@ -817,7 +819,16 @@ SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event,
                 return 1;
             }
             /* No events found in the queue, call WaitEventTimeout to wait for an event. */
-            status = _this->WaitEventTimeout(_this, timeout);
+			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);
+			}
+            status = _this->WaitEventTimeout(_this, loop_timeout);
             /* Set wakeup_window to NULL without holding the lock. */
             _this->wakeup_window = NULL;
             if (status <= 0) {
@@ -872,16 +883,19 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
 {
     SDL_VideoDevice *_this = SDL_GetVideoDevice();
     SDL_Window *wakeup_window;
+	Uint32 start = 0;
     Uint32 expiration = 0;
 
-    if (timeout > 0)
-        expiration = SDL_GetTicks() + timeout;
+    if (timeout > 0) {
+		start = SDL_GetTicks();
+        expiration = start + timeout;
+	}
 
     if (timeout != 0 && _this && _this->WaitEventTimeout && _this->SendWakeupEvent && !SDL_events_need_polling()) {
         /* Look if a shown window is available to send the wakeup event. */
         wakeup_window = SDL_find_active_window(_this);
         if (wakeup_window) {
-            int status = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, timeout);
+            int status = SDL_WaitEventTimeout_Device(_this, wakeup_window, event, start, timeout);
 
             /* There may be implementation-defined conditions where the backend cannot
                reliably wait for the next event. If that happens, fall back to polling. */