Portably exit readline from C++11 thread with SDL2 - c++

Consider a simple program: (require -std=c++11 -lpthread -lreadline -lSDL2 when compile with g++ with pthread)
#include <iostream>
#include <thread>
#include <SDL2/SDL.h>
#include <cstdlib>
extern "C" {
#include <readline/readline.h>
}
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
auto window = SDL_CreateWindow("title", 0, 0, 200, 200, SDL_WINDOW_SHOWN);
std::thread console([](){
while (true) {
char *console_input_c_str = readline("> ");
if (console_input_c_str == NULL)
break;
std::cout << "line: " << console_input_c_str << '\n';
std::free(console_input_c_str);
}
});
while (true) {
SDL_Event event;
SDL_WaitEvent(&event);
std::cerr << "received event type "<<event.type<<'\n';
if(event.type == SDL_WINDOWEVENT &&
event.window.event == SDL_WINDOWEVENT_CLOSE)
break;
}
SDL_DestroyWindow(window);
SDL_Quit();
console.join();
}
The main thread creates a window window with SDL2, enter a SDL event loop, while the thread console repeatedly read from the console using readline. When the window is exited, it waits for the console thread to finish then exit.
The program works fine; however, how can I make the console thread to stop when the window is exited?
It's fine if the program only use a single thread. It's also fine if exiting the console readline quits the program.
Exit readline is easy, but there is a problem with all the answers there - pthread_kill or reset -Q is not portable.
Terminating a thread is easy (using std::terminate or .detach()), but the console is left in a bad state because readline don't terminate normally. To reset the console it's possible to use reset -Q, but that's not portable.
Using readline's alternative (async) interface doesn't work because SDL don't listen for events on the console. Using select is also not portable.

Actually, readline is not completely blocking, it calls rl_event_hook every 1/10 seconds (by default). It's possible to check whether the program is stopped inside the event hook, and if so do a longjmp or throw to get out of the function (in this example int is used, although it's possible to define your own exception class):
std::atomic<bool> stopped (false);
rl_event_hook = [](){
if (stopped)
throw 0;
return 0;
};
try {
/* something calls readline() */
} catch (int) {
rl_cleanup_after_signal();
}
return 0;
or by setting rl_done = 1: (this won't work when rl_event_hook is nullptr. Also note that rl_event_hook is not called when a key is held, it's necessary to override rl_getc_function to change the behavior. It's not recommended to modify rl_done from the other thread)
std::atomic<bool> stopped (false);
rl_event_hook = [](){
if (stopped)
rl_done = true;
return 0;
};
/* something calls readline() */
if (stopped) return 0;
/* process */
return 0;
and in the main thread use:
stopped = true;
console.join();

Related

How should I wait for thread to finish with a timeout using C++11?

I have Windows multi-threaded code that I'm trying to make portable by using the C++11 thread and synchronization classes. How should the main thread wait for a worker thread with a timeout? I tried using a condition variable, but it's possible for the main thread to miss the worker's notification by waiting too late. In Windows I would use an "event object" as shown below:
#include "Windows.h"
HANDLE hWorkerDoneEvent; // synchronization event
DWORD WINAPI ThreadFunc(LPVOID lpParameter)
{
printf("worker: launched; working\n");
Sleep(1000); // pretend to work
printf("worker: signal done\n");
SetEvent(hWorkerDoneEvent); // signal main thread that we're done working
printf("worker: exit\n");
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
hWorkerDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // create auto-reset event
if (hWorkerDoneEvent == NULL) {
printf("main: error creating event\n");
return 0;
}
printf("main: launch worker\n");
if (CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL) == NULL) { // create worker thread
printf("main: error launching thread\n");
return 0;
}
printf("main: delay a bit\n");
Sleep(2000); // demonstrate that it's impossible to miss the worker's signal by waiting too late
printf("main: wait for worker's done signal\n");
if (WaitForSingleObject(hWorkerDoneEvent, 5000) == WAIT_OBJECT_0) { // wait for worker's signal
printf("main: worker finished normally\n");
} else {
printf("main: worker timeout or error\n");
}
return 0;
}
The Windows program's output is as expected:
main: launch worker
main: delay a bit
worker: launched; working
worker: signal done
worker: exit
main: wait for worker's done signal
main: worker finished normally
My attempt to replicate the above using C++11:
#include "thread"
#include "chrono"
#include "condition_variable"
using namespace std;
condition_variable cvWorkerDone;
mutex mtxWorkerDone;
void ThreadFunc()
{
printf("worker: launched; working\n");
this_thread::sleep_for(std::chrono::milliseconds(1000)); // pretend to work
printf("worker: signal done\n");
cvWorkerDone.notify_all(); // signal main thread that we're done working
printf("worker: exit\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("main: launch worker\n");
thread worker(ThreadFunc);
printf("main: delay a bit\n");
this_thread::sleep_for(std::chrono::milliseconds(2000)); // causes timeout because we waited too late and missed worker's notification
printf("main: wait for worker's done signal\n");
unique_lock<mutex> lk(mtxWorkerDone);
if (cvWorkerDone.wait_for(lk, std::chrono::milliseconds(5000))) {
printf("main: worker finished normally\n");
} else {
printf("main: worker timeout or error\n");
}
worker.join();
return 0;
}
The C+11 program's output; note that the main thread times out even though the worker did its work and signaled:
main: launch worker
worker: launched; working
main: delay a bit
worker: signal done
worker: exit
main: wait for worker's done signal
main: worker timeout or error
In the C++11 code, the main thread incorrectly times out because notification isn't sticky, in other words notify_all only works if a thread is already waiting. I understand that std::binary_semaphore would solve my problem, but that's only available in C++20 which I don't currently have access to. And yes, I could put a short wait at the start of the worker to give the main thread time to get to its wait, but that's gross and race-prone and inefficient.
I also tried having the worker lock a timed_mutex while the main thread does a try_lock_for on that same mutex, as shown below, but this is equally wrong (it deadlocks):
#include "thread"
#include "chrono"
#include "mutex"
using namespace std;
timed_mutex mtxWorkerDone;
void ThreadFunc()
{
printf("worker: launched\n");
printf("worker: delay a bit\n");
this_thread::sleep_for(std::chrono::milliseconds(500)); // fools main thread into locking mutex before we do, causing deadlock
printf("worker: locking mutex\n");
unique_lock<timed_mutex> lk(mtxWorkerDone);
printf("worker: mutex locked; working\n");
this_thread::sleep_for(std::chrono::milliseconds(1000)); // pretend to work
printf("worker: exit (releasing lock)\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("main: launch worker\n");
thread worker(ThreadFunc);
printf("main: wait for worker's done signal\n");
unique_lock<timed_mutex> lk(mtxWorkerDone, defer_lock);
if (lk.try_lock_for(std::chrono::milliseconds(5000))) {
printf("main: worker finished normally\n");
} else {
printf("main: worker timeout or error\n");
}
worker.join();
return 0;
}
The output; the main thread thinks the worker finished normally, but the worker is actually blocked at the unique_lock and never gets to its work.
main: launch worker
worker: launched
worker: delay a bit
main: wait for worker's done signal
main: worker finished normally
worker: locking mutex
To reiterate, I'm looking for a stdlib solution in C++11 that doesn't depend on sleeping to avoid races. I have often ported firmware from Windows to embedded platforms, and often faced similar issues. Windows is the Cadillac of synchronization APIs, with a tool for every occasion. Even twenty years later I still see posts asking how to replace WaitForMultipleObjects. Anyway I expected this to be a PITA and I'm not disappointed. :)
I very well may be missing something, and had to adapt your code a bit as I am doing this quickly on lunch, but I believe this is basically what you are looking for and as far as I can tell is the correct way to go about this. Note the addition of a lambda to the wait() call and the lock_guard in ThreadFunc():
#include "thread"
#include "chrono"
#include "condition_variable"
using namespace std;
condition_variable cvWorkerDone;
mutex mtxWorkerDone;
bool finished = false;
void ThreadFunc()
{
printf("worker: launched; working\n");
this_thread::sleep_for(std::chrono::milliseconds(1000)); // pretend to work
printf("worker: signal done\n");
std::lock_guard<std::mutex> lk(mtxWorkerDone);
finished = true;
cvWorkerDone.notify_all(); // signal main thread that we're done working
printf("worker: exit\n");
}
int main(int argc, char* argv[])
{
printf("main: launch worker\n");
thread worker(ThreadFunc);
printf("main: delay a bit\n");
this_thread::sleep_for(std::chrono::milliseconds(2000)); // causes timeout because we waited too late and missed worker's notification
printf("main: wait for worker's done signal\n");
{
unique_lock<mutex> lk(mtxWorkerDone);
if (cvWorkerDone.wait_for(lk, std::chrono::milliseconds(5000), []{return finished;})) {
printf("main: worker finished normally\n");
} else {
printf("main: worker timeout or error\n");
}
}
worker.join();
return 0;
}

SDL2: Sometimes make segement fault while Rendering and Event Polling in the different thread

So in my project I make a Event thread to catch the sdl event and may be pass it to the main thread to rander. But sometimes I get the Segementfault.
this is my test code.
#include "SDL2/SDL.h"
#include <SDL2/SDL_timer.h>
#include <iostream>
#include <thread>
using namespace std;
int main() {
SDL_Init(SDL_INIT_EVERYTHING);
bool running = true;
/* this is my event thread */
auto run = [&] {
for (; running; ) {
SDL_Event event; SDL_PollEvent(&event);
// ...
SDL_Delay(10);
}
};
thread t(run);
/* and the main is the randering thread */
for (int i = 0; i < 10; ++i) {
SDL_Window *window = SDL_CreateWindow("cycle. ",
100,
100,
600,
600,
SDL_WINDOW_SHOWN);
SDL_Renderer *screen = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// ... do some rander ...
// may be i will get some task to rander use the blocking queue
SDL_Delay(500);
SDL_DestroyRenderer(screen);
SDL_DestroyWindow(window);
}
running = false;
t.join();
SDL_Quit();
}
I want to know why I get the segement fault.
Should I make the Event listening and the rander in the same thead? (This will cause another problem: If placed in the same thread, when I set the loop to only 60 times per second, it also means that I can only render 60 times per second. This will cause my program to become stuck.)
Fundamentally, you should try to call SDL from a single thread. Even if you decide you need to multithread your program, you should do work in other threads and then synchronize that work to the main thread, which should use SDL to render / event-handle / etc.
Your program may be segfaulting because you're attempting to join() a thread that already join()-ed, so you want to at least check joinable() before you do so.
You also need to call PollEvent in a loop, since multiple events may be queued here.
But, especially since you're using Delays, it seems like you won't need the possible performance gained by multithreading your events. I would therefore suggest something like this:
#include <SDL2/SDL.h>
#include <iostream>
#include <thread>
using namespace std;
int main() {
SDL_Init(SDL_INIT_EVERYTHING);
bool running = true;
SDL_Window *window =
SDL_CreateWindow("cycle. ", 100, 100, 600, 600, SDL_WINDOW_SHOWN);
SDL_Renderer *screen =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
/* and the main is the randering thread */
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// TODO: handle events
// for example, set `running` to false when the window is closed or
// escape is pressed
// ...
}
// TODO: render here
// ...
}
SDL_DestroyRenderer(screen);
SDL_DestroyWindow(window);
SDL_Quit();
}
I'm not going to assume what you do and don't need, so here's a possible solution where you multithread the events (but you really, really, really should only use this if you absolutely need it).
This does the same as your code, except it calls PollEvent in the same thread as the rendering (the main thread), but handles the events somewhere else.
For this we use a queue of events, and a mutex to make sure it's thread-safe. I'm just putting this here for completeness' sake, and you probably (definitely) don't need this.
#include <SDL2/SDL.h>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
int main() {
SDL_Init(SDL_INIT_EVERYTHING);
bool running = true;
std::mutex eventQueueMutex;
std::queue<SDL_Event> eventQueue;
auto handleEvents = [&running, &eventQueue, &eventQueueMutex] {
while (running) {
{ // scope for unique_lock
std::unique_lock<std::mutex> lock(eventQueueMutex);
if (!eventQueue.empty()) {
auto event = eventQueue.front();
eventQueue.pop();
lock.unlock();
// TODO: handle the event here
}
} // end of scope for unique_lock
std::this_thread::sleep_for(
std::chrono::milliseconds(16)); // adjust this to your needs
}
};
std::thread eventHandlerThread = std::thread(handleEvents);
SDL_Window *window =
SDL_CreateWindow("cycle. ", 100, 100, 600, 600, SDL_WINDOW_SHOWN);
SDL_Renderer *screen =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
/* and the main is the randering thread */
while (running) {
SDL_Event event;
eventQueueMutex.lock();
while (SDL_PollEvent(&event)) {
eventQueue.push(event); // copy event into queue
}
eventQueueMutex.unlock();
// TODO: render here
// ...
}
if (eventHandlerThread.joinable()) {
eventHandlerThread.join();
}
SDL_DestroyRenderer(screen);
SDL_DestroyWindow(window);
SDL_Quit();
}
In both of these examples, check for my TODO comments to see where you write your code.

c++11 thread not ending in SDL

I have two thread which Displays the color changing SDL window.It compiles and runs fine, But I cant close them by clicking the close button of the window.
here is my code:-
#include <iostream>
#include <SDL2/SDL.h>
#include <thread>
using namespace std;
void th(string name,int x,int y,int w,int h,Uint32 flags)
{
int i=0,o=0,p=0;
SDL_Window *win;
SDL_Event e;
win = SDL_CreateWindow(name.c_str(),x,y,w,h,flags);
SDL_ShowWindow(win);
SDL_Surface *win_sur = SDL_GetWindowSurface(win);
SDL_FillRect(win_sur,0,SDL_MapRGB(win_sur->format,i,o,p));
SDL_UpdateWindowSurface(win);
while(1)
{
while(SDL_PollEvent(&e))
{
if(e.type == SDL_QUIT)
goto end;
}
SDL_FillRect(win_sur,0,SDL_MapRGB(win_sur->format,i,o,p));
SDL_UpdateWindowSurface(win);
i++;o+=2;p+=3;
SDL_Delay(10);
}
end:
SDL_DestroyWindow(win);
}
int main()
{
int lk =0;
SDL_Init(SDL_INIT_VIDEO);
thread t1(th,"win1",90,100,500,500,SDL_WINDOW_OPENGL);
thread t2(th,"win2",10,400,500,500,SDL_WINDOW_OPENGL);
t1.join();
t2.join();
SDL_Quit();
}
I am using gcc 7.3 and Linux
From the docs:
you can only call this function in the thread that set the video mode
In your case, since you call SDL_Init(SDL_INIT_VIDEO) in the main thread, you can only call SDL_PollEvent there as well. It probably never returns anything in your other threads.
Also, SDL_QUIT is generated when the user clicks on the close button of the last existing window. Check out SDL_WINDOWEVENT_CLOSE when you have multiple windows (or even by default, it's probably the better choice anyway).

Taking in user input from a different thread

I'm making a timer program the counts the time passed since the program started. In the background I'm also checking for keyboard input (enter/return to exit, click on the window); this is done in a separate thread I've run as detached.
It seems the second thread cannot receive the input from the main thread. When I use the keyboard or mouse, nothing happens. Also, nothing appears on the screen, just white.
std::mutex g_mutex;
std::condition_variable cv;
// check for input from the user using the window object
// sets stopProgram to true if the user wishes to exit
void poll(sf::RenderWindow& window, bool& stopProgram) {
std::unique_lock<std::mutex> lk(g_mutex);
// wait for main thread to open window
cv.wait(lk, [&] { return !stopProgram && window.isOpen(); });
sf::Event event;
while (true) {
if (window.pollEvent(event)) {
// if user wants to exit program
if (event.type == sf::Event::Closed || (event.type == sf::Event::KeyPressed &&
(event.key.code == sf::Keyboard::Return || event.key.code == sf::Keyboard::Escape))) {
window.close();
// main thread will explicitly exit the main loop
stopProgram = true;
break;
}
}
}
}
int main()
{
int hour = 0, minute = 0, second = 0;
auto text = textObject();
bool stopProgram = false;
// run a background thread that checks for input while the main program runs
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
std::once_flag flag;
std::lock_guard<std::mutex> lk(g_mutex);
while (window.isOpen()) {
// notify once window opens
std::call_once(flag, [&] { cv.notify_one(); });
// set timestamp
text->setString(makeTimeStamp(hour, minute, second));
// if the background thread set stopProgram, end the program
if (stopProgram) break;
window.clear(sf::Color::White);
window.draw(*text);
window.display();
// update time
second = (second + 1) % MAX_SEC;
if (second == 0) minute = (minute + 1) % MAX_MIN;
if (second == 0 && minute == 0) hour = (hour + 1) % MAX_HOUR;
// sleep one second
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Is my use of multithreading correct? If so, can only the main thread receive input and that's why it's not working?
Update: If I get rid of the while (true) and use while (window.pollEvent(event)) and move the lock_guard to just before if (stopProgram) then the text (timestamp) appears on the screen, but I still cannot process input.
main thread launches poll thread.
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
main thread acquires g_mutex and never ever releases it.
std::lock_guard<std::mutex> lk(g_mutex);
poll thread waits for g_mutex to be released:
std::unique_lock<std::mutex> lk(g_mutex);
but the main thread never releases it, so the poll thread never does anything.
To fix it. Change the beginning of the main() function:
int main()
{
int hour = 0, minute = 0, second = 0;
auto text = textObject();
volatile bool stopProgram = false;
// run a background thread that checks for input while the main program runs
std::thread(poll, std::ref(window), std::ref(stopProgram)).detach();
while (!window.isOpen()) { /* busy loop */ }
{
std::lock_guard<std::mutex> lk(g_mutex);
cv.notify_all();
}
while (window.isOpen()) {
...
This SFML API makes things more difficult than other windowing frameworks that I've used. It would be sooo useful if there were a thread-safe window.pushCustomEvent() function.

SDL Event Handling Delay

I am getting a major (1-2 second) delay between key presses.
Here is main.cpp (the lagging input handling):
#include <iostream>
#include "src/Input/InputManager.h"
#include "src/Graphics/Display.h"
#define LOG(x) std::cout << x << std::endl;
using namespace Rambug;
int main(int arc, char** argv)
{
Graphics::Display display(900, 600, "Rambug Engine Tester", true);
display.createDisplay();
SDL_Event event;
Input::InputManager inputManager;
// "Game" Loop
while (!display.isClosed())
{
display.update();
glClearColor(0.0f, 0.02f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
while (SDL_PollEvent(&event))
{
if (event.type == SDL_KEYDOWN)
{
std::cout << "Keydowner" << std::endl;
}
if (event.type == SDL_KEYUP)
{
std::cout << "Keyupper" << std::endl;
}
}
// inputManager.update();
}
display.destroyDisplay();
system("PAUSE");
return 0;
}
Here is Display.cpp, which runs PERFECTLY without any delay when I run the same code (SDL_KEYDOWN, SDL_KEYUP) I just run SDL_QUIT down there.
#include "Display.h"
namespace Rambug
{
namespace Graphics
{
Display::Display(int width, int height, std::string title, bool log)
{
m_displayWidth = width;
m_displayHeight = height;
m_displayTitle = title;
m_log = log;
m_window = nullptr;
}
Display::Display()
{
}
Display::~Display()
{
}
void Display::createDisplay()
{
// Initialize SDL
SDL_Init(SDL_INIT_EVERYTHING);
// Setting attributes to our window
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Create window
m_window = SDL_CreateWindow((m_displayTitle.c_str()), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_displayWidth, m_displayHeight, SDL_WINDOW_OPENGL);
// Error Check Window
if (m_window == nullptr)
{
if (m_log)
std::cerr << "Window could not be created!" << std::endl;
}
else
{
if (m_log)
std::cout << "Window Created Successfully With SDL!" << std::endl;
}
// Create OpenGL Context
m_glContext = SDL_GL_CreateContext(m_window);
// Initialize GLEW
glewExperimental = GL_TRUE;
GLenum status = glewInit();
if (glewExperimental)
{
if (m_log)
std::cout << "Glew Experimental: On" << std::endl;
}
// Error Check GLEW
if (status != GLEW_OK)
{
if (m_log)
{
std::cerr << "GLEW could not be initialized!" << std::endl;
}
}
else
{
if (m_log)
{
std::cout << "GLEW Was Initilized Successfully!" << std::endl;
}
}
// Log OpenGL Version Number
if (m_log)
{
std::cout << "Using OpenGL Version: " << glGetString(GL_VERSION) << std::endl;
}
m_closed = false;
}
void Display::destroyDisplay()
{
SDL_GL_DeleteContext(m_glContext);
SDL_DestroyWindow(m_window);
SDL_Quit();
}
void Display::update()
{
SDL_GL_SwapWindow(m_window);
// Check for Input
while (SDL_PollEvent(&m_sdlEvent))
{
if (m_sdlEvent.type == SDL_QUIT)
{
m_closed = true;
}
}
}
bool Display::isClosed()
{
return m_closed;
}
}
}
I also tried experimenting with an Input manager class, but that was the same deal: delays. The update method is what I would call in main.cpp (I believe that it is commented out)
#include "InputManager.h"
#include <iostream>
#define LOG(x) std::cout << x << std::endl;
namespace Rambug
{
namespace Input
{
InputManager::InputManager()
{
}
InputManager::~InputManager()
{
}
void InputManager::keyPressed(unsigned int keyCode)
{
m_keyMap[keyCode] = true;
}
void InputManager::keyReleased(unsigned int keyCode)
{
m_keyMap[keyCode] = false;
}
bool InputManager::isKeyDown(unsigned int keyCode)
{
auto it = m_keyMap.find(keyCode);
if (it != m_keyMap.end())
{
return it->second;
}
else
{
return false;
}
}
void InputManager::update()
{
while (SDL_PollEvent(&m_event))
{
switch (m_event.type)
{
case SDL_KEYDOWN:
LOG("SDL_KEYDOWN");
keyPressed(m_event.key.keysym.sym);
break;
case SDL_KEYUP:
LOG("SDL_KEYUP");
keyReleased(m_event.key.keysym.sym);
break;
}
}
}
}
}
So InputManager and main.cpp have major delays, while Display.cpp runs perfectly. Is it because I cannot run SDL_PollEvents twice?
Is it because I cannot run SDL_PollEvents twice?
Your problem isn't what I'd expect, but, yes, it's a bad idea to run SDL_PollEvents twice. SDL keeps an event stack which is added to as your program runs. SDL_PollEvents pops events from the stack until it is empty. As a result, running two polling loops, one will remove events which the other will then not see. Blind luck (or execution bottlenecks) will determine which loop is more likely to see any particular event occur. (See http://wiki.libsdl.org/SDL_PollEvent).
If you really want to run two polling loops, you can store unhandled events in your default case, and push the list of events back after each loop with SDL_PushEvent: http://wiki.libsdl.org/SDL_PushEvent
This said, I'm surprised that your events "get through" after a delay: I would expect them to vanish. Are you holding the keys down? Then, your OS key-repeat delay might be what you're seeing, after which the event queue is being flooded between each loop. You might want to check the repeat flag of the key event: http://wiki.libsdl.org/SDL_KeyboardEvent
I would say this points to a design problem. You should ask yourself, why does the Display delegate the Game ending? Would it not be more sensible to inform the Display, along with everything else, of this fact?
SDL keeps an event stack which is added to as your program runs. SDL_PollEvents pops events from the stack until it is empty.
I am fairly sure that it is not a stack, but a queue. The SDL_PushEvent has a somewhat misleading name there; what it really does is shove the event back into the queue from the "wrong" end. (It might be implemented internally as a stack, but it's behaviour is that of a queue.)
Still, Qualia's answer is the right way to go.
However, I don't agree that it is necessarily bad to have multiple event loops - they can be very useful. 2 scenarios:
1) You catch something like a resize event in your main event loop. If the ensuing operations are very time-consuming the event queue will be flooded with more resize events as long as the user keeps resizing the window.
In this case, on can have a separate event loop after the time-consuming repaint, which simply loops until it finds the first non-resize event, then pushes the last 2 events it saw back into the queue, and returns control to the main loop. That way you can discard the accumulated resize events. (It can be done more elegantly using the SDL_PeepEvents function, especially if there is a really huge pile-up of events in the queue.)
2) The action your program takes after catching a specific event will trigger other events, like when using SDL_RaiseWindow, which may trigger a multitude of focus and window related subsequent events, especially if you have more than one SDL window. Here, having a separate event loop can be used to deal with these triggered events, especially if the response to these events should be suppressed.
On the issue of delays, I have also encountered all sorts of strange behaviour with the SDL_KEYDOWN event, usually the event being triggered multiple times, and definitely not related to OS key repetition. This seems to occur only when using SDL_PollEvent; SDL_WaitEventTimeout, even with the timeout delay set to just '1', seems to inhibit this strange behaviour. Interestingly, the SDL_KEYUP event does not exhibit this behaviour.