SDL: Fixed event pump starvation if the application frequently pushes its own events

From 31f8c3ef4409a93fafa894b78c2ce176bd0c3cf3 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 6 Jan 2022 11:27:44 -0800
Subject: [PATCH] Fixed event pump starvation if the application frequently
 pushes its own events

---
 src/events/SDL_events.c | 65 ++++++++++++++++++++++++++++-------------
 1 file changed, 44 insertions(+), 21 deletions(-)

diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index d1d931eb32d..60737f83449 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -59,6 +59,7 @@ static SDL_EventWatcher *SDL_event_watchers = NULL;
 static int SDL_event_watchers_count = 0;
 static SDL_bool SDL_event_watchers_dispatching = SDL_FALSE;
 static SDL_bool SDL_event_watchers_removed = SDL_FALSE;
+static SDL_atomic_t SDL_sentinel_pending;
 
 typedef struct {
     Uint32 bits[8];
@@ -479,6 +480,7 @@ SDL_StopEventLoop(void)
     SDL_EventQ.free = NULL;
     SDL_EventQ.wmmsg_used = NULL;
     SDL_EventQ.wmmsg_free = NULL;
+    SDL_AtomicSet(&SDL_sentinel_pending, 0);
 
     /* Clear disabled event state */
     for (i = 0; i < SDL_arraysize(SDL_disabled_events); ++i) {
@@ -574,7 +576,9 @@ SDL_AddEvent(SDL_Event * event)
     }
 
     entry->event = *event;
-    if (event->type == SDL_SYSWMEVENT) {
+    if (event->type == SDL_POLLSENTINEL) {
+        SDL_AtomicAdd(&SDL_sentinel_pending, 1);
+    } else if (event->type == SDL_SYSWMEVENT) {
         entry->msg = *event->syswm.msg;
         entry->event.syswm.msg = &entry->msg;
     }
@@ -620,6 +624,10 @@ SDL_CutEvent(SDL_EventEntry *entry)
         SDL_EventQ.tail = entry->prev;
     }
 
+    if (entry->event.type == SDL_POLLSENTINEL) {
+        SDL_AtomicAdd(&SDL_sentinel_pending, -1);
+    }
+
     entry->next = SDL_EventQ.free;
     SDL_EventQ.free = entry;
     SDL_assert(SDL_AtomicGet(&SDL_EventQ.count) > 0);
@@ -648,9 +656,9 @@ SDL_SendWakeupEvent()
 }
 
 /* Lock the event queue, take a peep at it, and unlock it */
-int
-SDL_PeepEvents(SDL_Event * events, int numevents, SDL_eventaction action,
-               Uint32 minType, Uint32 maxType)
+static int
+SDL_PeepEventsInternal(SDL_Event * events, int numevents, SDL_eventaction action,
+               Uint32 minType, Uint32 maxType, SDL_bool include_sentinel)
 {
     int i, used;
 
@@ -713,7 +721,9 @@ SDL_PeepEvents(SDL_Event * events, int numevents, SDL_eventaction action,
                             SDL_CutEvent(entry);
                         }
                     }
-                    ++used;
+                    if (type != SDL_POLLSENTINEL || include_sentinel) {
+                        ++used;
+                    }
                 }
             }
         }
@@ -730,6 +740,12 @@ SDL_PeepEvents(SDL_Event * events, int numevents, SDL_eventaction action,
 
     return (used);
 }
+int
+SDL_PeepEvents(SDL_Event * events, int numevents, SDL_eventaction action,
+               Uint32 minType, Uint32 maxType)
+{
+    return SDL_PeepEventsInternal(events, numevents, action, minType, maxType, SDL_FALSE);
+}
 
 SDL_bool
 SDL_HasEvent(Uint32 type)
@@ -815,6 +831,14 @@ SDL_PumpEvents(void)
 #endif
 
     SDL_SendPendingSignalEvents();  /* in case we had a signal handler fire, etc. */
+
+    if (SDL_GetEventState(SDL_POLLSENTINEL) == SDL_ENABLE) {
+        SDL_Event sentinel;
+
+        SDL_zero(sentinel);
+        sentinel.type = SDL_POLLSENTINEL;
+        SDL_PushEvent(&sentinel);
+    }
 }
 
 /* Public functions */
@@ -952,17 +976,27 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
     SDL_Window *wakeup_window;
     Uint32 start = 0;
     Uint32 expiration = 0;
+    SDL_bool include_sentinel = (timeout == 0) ? SDL_TRUE : SDL_FALSE;
+
+    /* If there isn't a poll sentinel event pending, pump events and add one */
+    if (SDL_AtomicGet(&SDL_sentinel_pending) == 0) {
+        SDL_PumpEvents();
+    }
 
     /* First check for existing events */
-    switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
+    switch (SDL_PeepEventsInternal(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT, include_sentinel)) {
     case -1:
         return 0;
     case 0:
+        if (timeout == 0) {
+            /* No events available, and no timeout */
+            return 0;
+        }
         break;
     default:
-        /* Check whether we have reached the end of the poll cycle, and no more events are left */
-        if (timeout == 0 && event && event->type == SDL_POLLSENTINEL) {
-            return (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1);
+        if (event && event->type == SDL_POLLSENTINEL) {
+            /* Reached the end of a poll cycle, and no timeout */
+            return 0;
         }
         /* Has existing events */
         return 1;
@@ -973,7 +1007,7 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
         expiration = start + timeout;
     }
 
-    if (timeout != 0 && _this && _this->WaitEventTimeout && _this->SendWakeupEvent && !SDL_events_need_polling()) {
+    if (_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) {
@@ -993,10 +1027,6 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
         case -1:
             return 0;
         case 0:
-            if (timeout == 0) {
-                /* Polling and no events, just return */
-                return 0;
-            }
             if (timeout > 0 && SDL_TICKS_PASSED(SDL_GetTicks(), expiration)) {
                 /* Timeout expired and no events */
                 return 0;
@@ -1004,13 +1034,6 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
             SDL_Delay(1);
             break;
         default:
-            if (timeout == 0 && SDL_GetEventState(SDL_POLLSENTINEL) == SDL_ENABLE) {
-                /* We are at the start of a poll cycle with at least one new event.
-                   Add a sentinel event to mark the end of the cycle. */
-                SDL_Event sentinel;
-                sentinel.type = SDL_POLLSENTINEL;
-                SDL_PushEvent(&sentinel);
-            }
             /* Has events */
             return 1;
         }