[Interest] QApplication event loop advancing with longjmp()

Nikos Chantziaras realnc at gmail.com
Wed Sep 26 22:23:09 CEST 2012


For the last couple of years, I'm being bothered by an event loop 
integration problem.  I have some code that runs its own main loop on 
program start, and events need to be handled through callbacks.  For 
example, if the code needs a line of text from the user, it calls:

   int getInput(char* buf, size_t buflen);

getInput() is what needs to be implemented in a platform-specific way. 
(In this case, Qt-specific.)

It's been more or less working OK for me for a long time, by simply calling:

   qApp->sendPostedEvents(0, QEvent::DeferredDelete);
   qApp->sendPostedEvents();
   qApp->processEvents(QEventLoop::WaitForMoreEvents
                       | QEventLoop::AllEvents);

in the getInput() function above while waiting for the user to complete 
his input.  There's a dozen of such callbacks, and in every one of them 
I do the manual event loop advancing.  Except on Mac OS X, where 
QEvent::DeferredDelete hogs the CPU, so no DeferredDelete there.  Which 
I'm sure can result in nasty issues.

Thiago Macieira once told me that this isn't really a good thing to do. 
  "It's a hack; don't do it".  Amazingly, most Qt front-ends of a 
similar nature to mine (meaning applications that implement a GUI for 
stuff like emulators, interpreters, virtual machines, etc.) actually do 
the same thing.  That's because the upstream developers of the core code 
don't want to spend a huge amount of work to refactor their (often quite 
big) codebases and make their main loop asynchronous.  And even if they 
did, that would be a huge bother for the people doing the Mac OS X and 
MS Windows ports of that software.  And that's understandable; after 
all, other GUI APIs (win32, Cocoa, Gtk) don't have a problem with this. 
  If Qt has a problem with it, then the usual consensus is "that's Qt's 
problem to solve."

Thiago suggested I use a thread for this; run the external main loop 
inside a QThread.  That sounds reasonable at first glance, but actually 
it doesn't work.  QThread also needs its event loop to run in order to 
process events.  So that wouldn't solve the problem, but instead just 
shift it from QApplication to QThread, and we're back at the beginning 
(I would have to manually advance the event loop of the QThread this time.)

Then I tried integrating both loops by using coroutines.  Yeah, sounds 
like a hack already.  Basically, what I tried is doing a longjmp() to 
the function that calls the blocking main loop, return from there so 
that Qt's event loop runs naturally, and then schedule that function 
again for execution (with QMetaObject::invokeMethod() and using 
Qt::QueuedConnection).  Once it's called again, longjmp() from there 
back-in inside the blocking main loop so that it continues running:


   static jmp_buf jmpToLibLoopCtx;
   static jmp_buf jmpToQtCtx;
   static bool hasPendingJmp = false;

   int main(int argc, char* argv[])
   {
       //...
       MyApplication* app = new MyApplication(argc, argv);
       QMetaObject::invokeMethod(app, "entry", Qt::QueuedConnection);
       return app->exec();
   }


   void MyApplication::entry()
   {
       if (hasPendingJmp) {
           hasPendingJmp = false;
           longjmp(jmpToLibLoopCtx, 1);
       }
       if (setjmp(jmpToQtCtx)) {
           // Return to Qt's event loop; we're the outermost
           // function blocking it.
           return;
       }
       blockForever(); // Start the external main loop.
   }

Then, inside the input callbacks like getInput():

   if (not setjmp(jmpToLibLoopCtx)) {
       // Jump to the Qt event loop so we can process
       // input events.
       hasPendingJmp = true;
       longjmp(jmpToQtCtx, 1);
   }
   // If we get here, we have our input ready waiting for
   // us in a buffer.

And finally, when the the blocking main loop is ready to be resume, for 
example because the user finished her input, then keyPressEvent() 
schedules entry() for execution:

   QMetaObject::invokeMethod(app, "entry", Qt::QueuedConnection);

But it doesn't work; after longjmp'ing from entry() back inside the main 
loop, all data is now garbage, including the "this" pointer, and the 
application segfaults.  Maybe when entry() returns, the stack frame is 
being invalidated, so that longjmp()'ing later back to the blocking main 
loop gets you in a non-existent stack frame.  Or maybe Qt is simply not 
compatible with longjmp().  Or I'm doing it wrong :-)  (And hopefully 
*that's* the problem here.)

In any event, I would like to ask if anyone else here ever successfully 
did something like that; happily combining a blocking external main loop 
with Qt's event loop without manually using processEvents(), or has any 
enlightening ideas on how this could be done.




More information about the Interest mailing list