Only drain all pending events when exiting outermost wxEventLoop

This is especially important under MSW, where the modality of the nested
event loops actually ends as soon as wxModalEventLoop::Exit() is called,
and so we must avoid dispatching any events in the current loop after it
happens or we risk reentering the same loop again, which could result in
e.g. parent modal dialog being closed before the child event loop
returns (because the event closing the former was dispatched from the
latter) and other unexpected sequences of events.

To prevent this from happening, only dispatch pending events after the
loop exit if it's the outermost loop, as there should be no danger in
doing it in this case. Conversely, we don't lose anything by not doing
this in nested event loops as the outer loop will take care of any
remaining pending events anyhow.

To make this work in an ABI-compatible way, add a global counter of the
currently existing event loops which is used to check if there is more
than one event loop currently running.

Closes #11273, #11573, #11269.
This commit is contained in:
Vadim Zeitlin
2018-07-04 19:38:10 +02:00
parent cf966718e6
commit a0298f3149
2 changed files with 35 additions and 14 deletions

View File

@@ -63,11 +63,8 @@
class WXDLLIMPEXP_BASE wxEventLoopBase class WXDLLIMPEXP_BASE wxEventLoopBase
{ {
public: public:
// trivial, but needed (because of wxEventLoopBase) ctor
wxEventLoopBase(); wxEventLoopBase();
virtual ~wxEventLoopBase();
// dtor
virtual ~wxEventLoopBase() { }
// use this to check whether the event loop was successfully created before // use this to check whether the event loop was successfully created before
// using it // using it

View File

@@ -24,6 +24,13 @@
#include "wx/scopeguard.h" #include "wx/scopeguard.h"
#include "wx/apptrait.h" #include "wx/apptrait.h"
#include "wx/private/eventloopsourcesmanager.h" #include "wx/private/eventloopsourcesmanager.h"
// Counts currently existing event loops.
//
// As wxEventLoop can be only used from the main thread, there is no need to
// protect accesses to this variable.
static int gs_eventLoopCount = 0;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// wxEventLoopBase // wxEventLoopBase
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -32,12 +39,19 @@ wxEventLoopBase *wxEventLoopBase::ms_activeLoop = NULL;
wxEventLoopBase::wxEventLoopBase() wxEventLoopBase::wxEventLoopBase()
{ {
gs_eventLoopCount++;
m_isInsideRun = false; m_isInsideRun = false;
m_shouldExit = false; m_shouldExit = false;
m_yieldLevel = 0; m_yieldLevel = 0;
m_eventsToProcessInsideYield = wxEVT_CATEGORY_ALL; m_eventsToProcessInsideYield = wxEVT_CATEGORY_ALL;
} }
wxEventLoopBase::~wxEventLoopBase()
{
gs_eventLoopCount--;
}
bool wxEventLoopBase::IsMain() const bool wxEventLoopBase::IsMain() const
{ {
if (wxTheApp) if (wxTheApp)
@@ -254,32 +268,42 @@ int wxEventLoopManual::DoRun()
OnNextIteration(); OnNextIteration();
// generate and process idle events for as long as we don't // generate and process idle events for as long as we don't
// have anything else to do // have anything else to do, but stop doing this if Exit() is
// called by one of the idle handlers
while ( !m_shouldExit && !Pending() && ProcessIdle() ) while ( !m_shouldExit && !Pending() && ProcessIdle() )
; ;
// if Exit() was called, don't dispatch any more events here
if ( m_shouldExit ) if ( m_shouldExit )
break; break;
// a message came or no more idle processing to do, dispatch // a message came or no more idle processing to do, dispatch
// all the pending events and call Dispatch() to wait for the // all the pending events and call Dispatch() to wait for the
// next message // next message
if ( !ProcessEvents() ) if ( !ProcessEvents() || m_shouldExit )
{
// we got WM_QUIT
break; break;
} }
}
// Process the remaining queued messages, both at the level of the // If we exit the outermost loop, process the remaining queued
// underlying toolkit level (Pending/Dispatch()) and wx level // messages, both at the level of the underlying toolkit level
// (Has/ProcessPendingEvents()). // (Pending/Dispatch()) and wx level (Has/ProcessPendingEvents()).
// //
// We do run the risk of never exiting this loop if pending event // Note that we must not do this for nested modal event loops as,
// at least in wxMSW, the modality has already been undone when
// Exit() was called, and so we could end up with reentrancies such
// as starting another modal event loop before exiting this one.
// To avoid this we must not dispatch any events after calling
// Exit() and, for the nested loops, there is no real reason to do
// it anyhow, as the outer loop will still dispatch them.
//
// Finally, even when doing this in the outermost loop, there is
// still the risk of never exiting this loop if pending event
// handlers endlessly generate new events but they shouldn't do // handlers endlessly generate new events but they shouldn't do
// this in a well-behaved program and we shouldn't just discard the // this in a well-behaved program and we shouldn't just discard the
// events we already have, they might be important. // events we already have, they might be important.
if ( gs_eventLoopCount != 1 )
break;
for ( ;; ) for ( ;; )
{ {
bool hasMoreEvents = false; bool hasMoreEvents = false;