From 5c6210e3452f78cab2f58887e747eb5cb2501f70 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Mon, 12 Jun 2017 14:37:39 +0200 Subject: [PATCH] Support more than 62 instances of QWinEventNotifier QWinEventNotifiers were limited to 62 instances, because of WaitForMultipleObject's limitation to MAXIMUM_WAIT_OBJECTS - 1 handles. Use the RegisterWaitForSingleObject API which does not have this restriction and executes waits in threads managed by the system. A central manual reset event per event dispatcher is signaled in the RegisterWaitForSingleObject callback and waited for in the event loop. [ChangeLog][QtCore][QWinEventNotifier] QWinEventNotifier is not restricted to 62 instances anymore. Task-number: QTBUG-8819 Change-Id: I2c749951453a4b699cc50dada0d6017440b67a4a Reviewed-by: Oliver Wolff --- src/corelib/kernel/kernel.pri | 3 +- src/corelib/kernel/qeventdispatcher_win.cpp | 67 ++++++++------ src/corelib/kernel/qeventdispatcher_win_p.h | 2 + src/corelib/kernel/qwineventnotifier.cpp | 60 ++++++++++--- src/corelib/kernel/qwineventnotifier_p.h | 81 +++++++++++++++++ .../tst_qwineventnotifier.cpp | 88 +++++++++++++++++++ 6 files changed, 260 insertions(+), 41 deletions(-) create mode 100644 src/corelib/kernel/qwineventnotifier_p.h diff --git a/src/corelib/kernel/kernel.pri b/src/corelib/kernel/kernel.pri index 29bd5bbc6c..8abe9b2b44 100644 --- a/src/corelib/kernel/kernel.pri +++ b/src/corelib/kernel/kernel.pri @@ -79,7 +79,8 @@ win32 { kernel/qsharedmemory_win.cpp \ kernel/qsystemsemaphore_win.cpp HEADERS += \ - kernel/qwineventnotifier.h + kernel/qwineventnotifier.h \ + kernel/qwineventnotifier_p.h winrt { SOURCES += kernel/qeventdispatcher_winrt.cpp diff --git a/src/corelib/kernel/qeventdispatcher_win.cpp b/src/corelib/kernel/qeventdispatcher_win.cpp index 0952464f53..fc34dd0a6b 100644 --- a/src/corelib/kernel/qeventdispatcher_win.cpp +++ b/src/corelib/kernel/qeventdispatcher_win.cpp @@ -53,6 +53,7 @@ #include "qcoreapplication_p.h" #include #include +#include QT_BEGIN_NAMESPACE @@ -96,13 +97,14 @@ LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPA QEventDispatcherWin32Private::QEventDispatcherWin32Private() : threadId(GetCurrentThreadId()), interrupt(false), closingDown(false), internalHwnd(0), getMessageHook(0), serialNumber(0), lastSerialNumber(0), sendPostedEventsWindowsTimerId(0), - wakeUps(0) - , activateNotifiersPosted(false) + wakeUps(0), activateNotifiersPosted(false), winEventNotifierActivatedEvent(NULL) { } QEventDispatcherWin32Private::~QEventDispatcherWin32Private() { + if (winEventNotifierActivatedEvent) + CloseHandle(winEventNotifierActivatedEvent); if (internalHwnd) DestroyWindow(internalHwnd); } @@ -537,12 +539,14 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) bool needWM_QT_SENDPOSTEDEVENTS = false; do { DWORD waitRet = 0; - HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1]; + DWORD nCount = 0; + HANDLE *pHandles = nullptr; + if (d->winEventNotifierActivatedEvent) { + nCount = 1; + pHandles = &d->winEventNotifierActivatedEvent; + } QVarLengthArray processedTimers; while (!d->interrupt) { - DWORD nCount = d->winEventNotifierList.count(); - Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1); - MSG msg; bool haveMessage; @@ -584,8 +588,6 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) } if (!haveMessage) { // no message - check for signalled objects - for (int i=0; i<(int)nCount; i++) - pHandles[i] = d->winEventNotifierList.at(i)->handle(); waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE); if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) { // a new message has arrived, process it @@ -626,7 +628,7 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) DispatchMessage(&msg); } } else if (waitRet - WAIT_OBJECT_0 < nCount) { - d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0)); + activateEventNotifiers(); } else { // nothing todo so break break; @@ -639,16 +641,11 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) && !d->interrupt && (flags & QEventLoop::WaitForMoreEvents)); if (canWait) { - DWORD nCount = d->winEventNotifierList.count(); - Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1); - for (int i=0; i<(int)nCount; i++) - pHandles[i] = d->winEventNotifierList.at(i)->handle(); - emit aboutToBlock(); waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); emit awake(); if (waitRet - WAIT_OBJECT_0 < nCount) { - d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0)); + activateEventNotifiers(); retVal = true; } } @@ -906,12 +903,12 @@ bool QEventDispatcherWin32::registerEventNotifier(QWinEventNotifier *notifier) if (d->winEventNotifierList.contains(notifier)) return true; - if (d->winEventNotifierList.count() >= MAXIMUM_WAIT_OBJECTS - 2) { - qWarning("QWinEventNotifier: Cannot have more than %d enabled at one time", MAXIMUM_WAIT_OBJECTS - 2); - return false; - } d->winEventNotifierList.append(notifier); - return true; + + if (!d->winEventNotifierActivatedEvent) + d->winEventNotifierActivatedEvent = CreateEvent(0, TRUE, FALSE, nullptr); + + return QWinEventNotifierPrivate::get(notifier)->registerWaitObject(); } void QEventDispatcherWin32::unregisterEventNotifier(QWinEventNotifier *notifier) @@ -927,17 +924,35 @@ void QEventDispatcherWin32::unregisterEventNotifier(QWinEventNotifier *notifier) Q_D(QEventDispatcherWin32); int i = d->winEventNotifierList.indexOf(notifier); - if (i != -1) - d->winEventNotifierList.takeAt(i); + if (i == -1) + return; + d->winEventNotifierList.takeAt(i); + QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier); + if (nd->waitHandle) + nd->unregisterWaitObject(); } void QEventDispatcherWin32::activateEventNotifiers() { Q_D(QEventDispatcherWin32); - //### this could break if events are removed/added in the activation - for (int i=0; iwinEventNotifierList.count(); i++) { - if (WaitForSingleObjectEx(d->winEventNotifierList.at(i)->handle(), 0, TRUE) == WAIT_OBJECT_0) - d->activateEventNotifier(d->winEventNotifierList.at(i)); + ResetEvent(d->winEventNotifierActivatedEvent); + + // Iterate backwards, because the notifier might remove itself on activate(). + for (int i = d->winEventNotifierList.count(); --i >= 0;) { + QWinEventNotifier *notifier = d->winEventNotifierList.at(i); + QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier); + if (WaitForSingleObject(nd->handleToEvent, 0) == WAIT_OBJECT_0) { + nd->unregisterWaitObject(); + d->activateEventNotifier(notifier); + } + } + + // Re-register the remaining activated notifiers. + for (int i = 0; i < d->winEventNotifierList.count(); ++i) { + QWinEventNotifier *notifier = d->winEventNotifierList.at(i); + QWinEventNotifierPrivate *nd = QWinEventNotifierPrivate::get(notifier); + if (nd->waitHandle || !nd->registerWaitObject()) + return; } } diff --git a/src/corelib/kernel/qeventdispatcher_win_p.h b/src/corelib/kernel/qeventdispatcher_win_p.h index f6d1bffdf5..683c7f8f36 100644 --- a/src/corelib/kernel/qeventdispatcher_win_p.h +++ b/src/corelib/kernel/qeventdispatcher_win_p.h @@ -161,6 +161,7 @@ class Q_CORE_EXPORT QEventDispatcherWin32Private : public QAbstractEventDispatch public: QEventDispatcherWin32Private(); ~QEventDispatcherWin32Private(); + static QEventDispatcherWin32Private *get(QEventDispatcherWin32 *q) { return q->d_func(); } DWORD threadId; @@ -192,6 +193,7 @@ public: void postActivateSocketNotifiers(); void doWsaAsyncSelect(int socket, long event); + HANDLE winEventNotifierActivatedEvent; QList winEventNotifierList; void activateEventNotifier(QWinEventNotifier * wen); diff --git a/src/corelib/kernel/qwineventnotifier.cpp b/src/corelib/kernel/qwineventnotifier.cpp index 0808374a6a..6bfa6ca729 100644 --- a/src/corelib/kernel/qwineventnotifier.cpp +++ b/src/corelib/kernel/qwineventnotifier.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "qwineventnotifier.h" +#include "qwineventnotifier_p.h" #ifdef Q_OS_WINRT #include "qeventdispatcher_winrt_p.h" @@ -50,19 +50,6 @@ QT_BEGIN_NAMESPACE -class QWinEventNotifierPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QWinEventNotifier) -public: - QWinEventNotifierPrivate() - : handleToEvent(0), enabled(false) {} - QWinEventNotifierPrivate(HANDLE h, bool e) - : handleToEvent(h), enabled(e) {} - - HANDLE handleToEvent; - bool enabled; -}; - /*! \class QWinEventNotifier \inmodule QtCore @@ -246,4 +233,49 @@ bool QWinEventNotifier::event(QEvent * e) return false; } +#if defined(Q_OS_WINRT) + +bool QWinEventNotifierPrivate::registerWaitObject() +{ + Q_UNIMPLEMENTED(); + return false; +} + +void QWinEventNotifierPrivate::unregisterWaitObject() +{ + Q_UNIMPLEMENTED(); +} + +#else // defined(Q_OS_WINRT) + +static void CALLBACK wfsoCallback(void *context, BOOLEAN /*ignore*/) +{ + QWinEventNotifierPrivate *nd = reinterpret_cast(context); + QAbstractEventDispatcher *eventDispatcher = nd->threadData->eventDispatcher.load(); + QEventDispatcherWin32Private *edp = QEventDispatcherWin32Private::get( + static_cast(eventDispatcher)); + SetEvent(edp->winEventNotifierActivatedEvent); +} + +bool QWinEventNotifierPrivate::registerWaitObject() +{ + if (RegisterWaitForSingleObject(&waitHandle, handleToEvent, wfsoCallback, this, + INFINITE, WT_EXECUTEONLYONCE) == 0) { + qErrnoWarning("QWinEventNotifier: RegisterWaitForSingleObject failed."); + return false; + } + return true; +} + +void QWinEventNotifierPrivate::unregisterWaitObject() +{ + // Unregister the wait handle and wait for pending callbacks to finish. + if (UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE)) + waitHandle = NULL; + else + qErrnoWarning("QWinEventNotifier: UnregisterWaitEx failed."); +} + +#endif // !defined(Q_OS_WINRT) + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qwineventnotifier_p.h b/src/corelib/kernel/qwineventnotifier_p.h new file mode 100644 index 0000000000..bddeaaf134 --- /dev/null +++ b/src/corelib/kernel/qwineventnotifier_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINEVENTNOTIFIER_P_H +#define QWINEVENTNOTIFIER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qwineventnotifier.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWinEventNotifierPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QWinEventNotifier) +public: + QWinEventNotifierPrivate() + : handleToEvent(0), enabled(false) {} + QWinEventNotifierPrivate(HANDLE h, bool e) + : handleToEvent(h), enabled(e) {} + + static QWinEventNotifierPrivate *get(QWinEventNotifier *q) { return q->d_func(); } + bool registerWaitObject(); + void unregisterWaitObject(); + + HANDLE handleToEvent; + HANDLE waitHandle = NULL; + bool enabled; +}; + +QT_END_NAMESPACE + +#endif // QWINEVENTNOTIFIER_P_H diff --git a/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp b/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp index 3221587300..304f6121a5 100644 --- a/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp +++ b/tests/auto/corelib/kernel/qwineventnotifier/tst_qwineventnotifier.cpp @@ -31,6 +31,8 @@ #include #include +#include + class tst_QWinEventNotifier : public QObject { Q_OBJECT @@ -40,6 +42,7 @@ protected slots: void simple_timerSet(); private slots: void simple(); + void manyNotifiers(); private: HANDLE simpleHEvent; @@ -87,6 +90,91 @@ void tst_QWinEventNotifier::simple() QVERIFY(simpleActivated); } +class EventWithNotifier : public QObject +{ + Q_OBJECT +public: + EventWithNotifier() + { + connect(¬ifier, &QWinEventNotifier::activated, + this, &EventWithNotifier::onNotifierActivated); + notifier.setHandle(CreateEvent(0, TRUE, FALSE, 0)); + notifier.setEnabled(true); + + static int nextIndex = 0; + idx = nextIndex++; + } + + ~EventWithNotifier() + { + notifier.setEnabled(false); + CloseHandle(notifier.handle()); + } + + HANDLE eventHandle() const { return notifier.handle(); } + int numberOfTimesActivated() const { return activatedCount; } + +signals: + void activated(); + +public slots: + void onNotifierActivated() + { + ResetEvent(notifier.handle()); + activatedCount++; + emit activated(); + } + +private: + QWinEventNotifier notifier; + int activatedCount = 0; + int idx = 0; +}; + +void tst_QWinEventNotifier::manyNotifiers() +{ + const size_t maxEvents = 100; + const size_t middleEvenEvent = maxEvents / 2; + Q_ASSERT(middleEvenEvent % 2 == 0); + using EventWithNotifierPtr = std::unique_ptr; + std::vector events(maxEvents); + std::generate(events.begin(), events.end(), [] () { + return EventWithNotifierPtr(new EventWithNotifier); + }); + + QTestEventLoop loop; + auto connection = connect(events.at(8).get(), &EventWithNotifier::activated, &loop, &QTestEventLoop::exitLoop); + for (const auto &ewn : events) { + connect(ewn.get(), &EventWithNotifier::activated, [&events, &loop] () { + if (std::all_of(events.cbegin(), events.cend(), + [] (const EventWithNotifierPtr &ewn) { + return ewn->numberOfTimesActivated() > 0; })) { + loop.exitLoop(); + } + }); + } + + // Activate all even events before running the event loop. + for (size_t i = 0; i < events.size(); i += 2) + SetEvent(events.at(i)->eventHandle()); + + // Wait until event notifier with index 8 has been activated. + loop.enterLoop(30); + QObject::disconnect(connection); + + // Activate all odd events after the event loop has run for a bit. + for (size_t i = 1; i < events.size(); i += 2) + SetEvent(events.at(i)->eventHandle()); + + // Wait until all event notifiers have fired. + loop.enterLoop(30); + + // All notifiers must have been activated exactly once. + QVERIFY(std::all_of(events.cbegin(), events.cend(), [] (const EventWithNotifierPtr &ewn) { + return ewn->numberOfTimesActivated() == 1; + })); +} + QTEST_MAIN(tst_QWinEventNotifier) #include "tst_qwineventnotifier.moc"