QEventDispatcherWin32: redesign event notifiers activation

The previous implementation multiplexed callback-based event
notification into a single proxy event (cf. 85403d0af), which was
in turn object-waited for (this was the case since the beginning
of public qt history). It makes more sense to multiplex into a
posted message, because that also works with foreign event loops
that do not know anything about our event objects.

Task-number: QTBUG-64443
Change-Id: I97945ac8b5d7c8582701077134c0aef4f3b5a18f
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
bb10
Alex Trotsenko 2019-11-16 20:39:20 +02:00
parent 9d55d6b45b
commit 8969070cfd
4 changed files with 150 additions and 79 deletions

View File

@ -99,14 +99,12 @@ LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPA
QEventDispatcherWin32Private::QEventDispatcherWin32Private()
: threadId(GetCurrentThreadId()), interrupt(false), internalHwnd(0),
getMessageHook(0), sendPostedEventsTimerId(0), wakeUps(0),
activateNotifiersPosted(false), winEventNotifierActivatedEvent(NULL)
activateNotifiersPosted(false), activateEventNotifiersPosted(false)
{
}
QEventDispatcherWin32Private::~QEventDispatcherWin32Private()
{
if (winEventNotifierActivatedEvent)
CloseHandle(winEventNotifierActivatedEvent);
if (internalHwnd)
DestroyWindow(internalHwnd);
}
@ -457,6 +455,14 @@ void QEventDispatcherWin32Private::postActivateSocketNotifiers()
activateNotifiersPosted = PostMessage(internalHwnd, WM_QT_ACTIVATENOTIFIERS, 0, 0);
}
void QEventDispatcherWin32Private::postActivateEventNotifiers()
{
Q_Q(QEventDispatcherWin32);
if (!activateEventNotifiersPosted.fetchAndStoreRelease(true))
QCoreApplication::postEvent(q, new QEvent(QEvent::WinEventAct));
}
void QEventDispatcherWin32::createInternalHwnd()
{
Q_D(QEventDispatcherWin32);
@ -526,102 +532,78 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
bool canWait;
bool retVal = false;
do {
DWORD waitRet = 0;
DWORD nCount = 0;
HANDLE *pHandles = nullptr;
if (d->winEventNotifierActivatedEvent) {
nCount = 1;
pHandles = &d->winEventNotifierActivatedEvent;
}
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt.loadRelaxed()) {
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// queue socket events for later processing
d->queuedSocketEvents.append(msg);
continue;
}
}
}
if (!haveMessage) {
// no message - check for signalled objects
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
// a new message has arrived, process it
} else if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
}
if (haveMessage) {
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
// Set result to 'true' because the message was sent by wakeUp().
retVal = true;
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// queue socket events for later processing
d->queuedSocketEvents.append(msg);
continue;
}
if (msg.message == WM_TIMER) {
// Skip timer event intended for use inside foreign loop.
if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId)
continue;
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count(); ++i) {
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);
} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance())
QCoreApplication::instance()->quit();
return false;
}
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
} else if (MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALERTABLE)
== WAIT_OBJECT_0) {
// a new message has arrived, process it
continue;
} else {
// nothing todo so break
// nothing to do, so break
break;
}
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
// Set result to 'true' because the message was sent by wakeUp().
retVal = true;
continue;
}
if (msg.message == WM_TIMER) {
// Skip timer event intended for use inside foreign loop.
if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId)
continue;
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count(); ++i) {
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);
} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance())
QCoreApplication::instance()->quit();
return false;
}
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
retVal = true;
}
// still nothing - wait for message or signalled objects
// wait for message
canWait = (!retVal
&& !d->interrupt.loadRelaxed()
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait) {
emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
retVal = true;
}
}
} while (canWait);
@ -871,12 +853,11 @@ bool QEventDispatcherWin32::registerEventNotifier(QWinEventNotifier *notifier)
if (d->winEventNotifierList.contains(notifier))
return true;
createInternalHwnd();
d->winEventNotifierList.append(notifier);
d->winEventNotifierListModified = true;
if (!d->winEventNotifierActivatedEvent)
d->winEventNotifierActivatedEvent = CreateEvent(0, TRUE, FALSE, nullptr);
return QWinEventNotifierPrivate::get(notifier)->registerWaitObject();
}
@ -909,7 +890,9 @@ void QEventDispatcherWin32::doUnregisterEventNotifier(QWinEventNotifier *notifie
void QEventDispatcherWin32::activateEventNotifiers()
{
Q_D(QEventDispatcherWin32);
ResetEvent(d->winEventNotifierActivatedEvent);
// Enable WM_QT_ACTIVATEWINEVENTS posting.
d->activateEventNotifiersPosted.fetchAndStoreAcquire(false);
// Activate signaled notifiers. Our winEventNotifierList can be modified in activation slots.
do {
@ -1055,6 +1038,9 @@ bool QEventDispatcherWin32::event(QEvent *e)
case QEvent::Timer:
d->sendTimerEvent(static_cast<const QTimerEvent*>(e)->timerId());
break;
case QEvent::WinEventAct:
activateEventNotifiers();
break;
default:
break;
}

View File

@ -54,6 +54,7 @@
#include "QtCore/qabstracteventdispatcher.h"
#include "QtCore/qt_windows.h"
#include "QtCore/qhash.h"
#include "QtCore/qatomic.h"
#include "qabstracteventdispatcher_p.h"
@ -193,8 +194,9 @@ public:
bool closingDown = false;
bool winEventNotifierListModified = false;
HANDLE winEventNotifierActivatedEvent;
QAtomicInt activateEventNotifiersPosted;
QList<QWinEventNotifier *> winEventNotifierList;
void postActivateEventNotifiers();
void activateEventNotifier(QWinEventNotifier * wen);
QList<MSG> queuedUserInputEvents;

View File

@ -246,7 +246,7 @@ static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/)
QEventDispatcherWin32Private *edp = QEventDispatcherWin32Private::get(
static_cast<QEventDispatcherWin32 *>(eventDispatcher));
++nd->signaledCount;
SetEvent(edp->winEventNotifierActivatedEvent);
edp->postActivateEventNotifiers();
}
bool QWinEventNotifierPrivate::registerWaitObject()

View File

@ -37,6 +37,7 @@
#include <QtNetwork/qtcpsocket.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qwineventnotifier.h>
#include <QtCore/qt_windows.h>
@ -50,6 +51,8 @@ class tst_NoQtEventLoop : public QObject
private slots:
void consumeMouseEvents();
void consumeSocketEvents();
void consumeWinEvents_data();
void consumeWinEvents();
void deliverEventsInLivelock();
void postingWithNoYieldFlag();
};
@ -314,6 +317,86 @@ void tst_NoQtEventLoop::consumeSocketEvents()
QVERIFY(server.hasPendingConnections());
}
void tst_NoQtEventLoop::consumeWinEvents_data()
{
QTest::addColumn<bool>("peeking");
QTest::addColumn<bool>("nestedLoops");
QTest::addColumn<bool>("nativeMainLoop");
QTest::newRow("PeekMessage, single loop") << true << false << true;
QTest::newRow("GetMessage, single loop") << false << false << true;
QTest::newRow("PeekMessage, nested loops") << true << true << true;
QTest::newRow("GetMessage, nested loops") << false << true << true;
QTest::newRow("PeekMessage, nested loops, Qt main loop") << true << true << false;
QTest::newRow("GetMessage, nested loops, Qt main loop") << false << true << false;
}
void tst_NoQtEventLoop::consumeWinEvents()
{
QFETCH(bool, peeking);
QFETCH(bool, nestedLoops);
QFETCH(bool, nativeMainLoop);
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
HANDLE winEvent = ::CreateEvent(0, FALSE, FALSE, 0);
QVERIFY(winEvent != NULL);
bool notifierActivated = false;
QWinEventNotifier notifier(winEvent);
connect(&notifier, &QWinEventNotifier::activated, [&notifierActivated]() {
notifierActivated = true;
});
bool timeExpired = false;
QTimer expiredTimer;
connect(&expiredTimer, &QTimer::timeout, [&timeExpired]() {
timeExpired = true;
});
expiredTimer.start(3000);
auto runNativeEventLoop = [&timeExpired, &notifierActivated, peeking]() -> void {
MSG msg;
while (!(timeExpired || notifierActivated)) {
if (peeking) {
if (!::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
QThread::msleep(100);
continue;
}
} else {
if (::GetMessage(&msg, NULL, 0, 0) <= 0)
break;
}
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
};
QTimer::singleShot(0, [winEvent, nestedLoops, runNativeEventLoop]() {
::SetEvent(winEvent);
if (nestedLoops)
runNativeEventLoop();
});
// Exec main message loop
if (nativeMainLoop) {
runNativeEventLoop();
} else {
QEventLoop loop;
connect(&notifier, &QWinEventNotifier::activated,
&loop, &QEventLoop::quit);
connect(&expiredTimer, &QTimer::timeout,
&loop, &QEventLoop::quit);
loop.exec();
}
QVERIFY(notifierActivated);
QVERIFY(!timeExpired);
}
void tst_NoQtEventLoop::deliverEventsInLivelock()
{
int argc = 1;