From 468ac1fefb2f8996359df8f74f05f59c7b4a6fdf Mon Sep 17 00:00:00 2001 From: Giulio Camuffo Date: Mon, 16 Mar 2020 10:22:36 +0100 Subject: [PATCH] Move the wayland socket polling to a separate event thread New event threads is introduced which calls poll() on the wayland fd, instead of relying on the event dispatcher by using the QSocketNotifier. This allows to call in the proper order the wl_display_prepare_read(), poll() and wl_display_read_events() functions. One thread is responsible for the default queue; when needed, it emit a signal so that the main thread can dispatch the queue. Another thread is responsible for the dedicated queue for frame callbacks; this thread will dispatch events on the thread itself. QWaylandWindow is updated to, instead of each window's dedicated event queue, use this queue for frame callbacks. Co-authored-by: Ratchanan Srirattanamet Task-number: QTBUG-66075 Change-Id: Ibb33ad7f4193b866d1b8d7a0405a94d59dcad5eb Reviewed-by: Qt CI Bot Reviewed-by: Eskil Abrahamsen Blomfeldt --- .../platforms/wayland/qwaylanddisplay.cpp | 303 +++++++++++++----- .../platforms/wayland/qwaylanddisplay_p.h | 20 +- .../platforms/wayland/qwaylandintegration.cpp | 6 +- .../platforms/wayland/qwaylandwindow.cpp | 34 +- .../platforms/wayland/qwaylandwindow_p.h | 2 +- tests/auto/wayland/CMakeLists.txt | 1 + .../auto/wayland/multithreaded/CMakeLists.txt | 12 + .../multithreaded/tst_multithreaded.cpp | 165 ++++++++++ 8 files changed, 429 insertions(+), 114 deletions(-) create mode 100644 tests/auto/wayland/multithreaded/CMakeLists.txt create mode 100644 tests/auto/wayland/multithreaded/tst_multithreaded.cpp diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 9ae6abd90a3..c56351caab4 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -90,10 +90,201 @@ #include // for std::tie +static void checkWaylandError(struct wl_display *display) +{ + int ecode = wl_display_get_error(display); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + // special case this to provide a nicer error + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(1); +} + QT_BEGIN_NAMESPACE namespace QtWaylandClient { +class EventThread : public QThread +{ + Q_OBJECT +public: + enum OperatingMode { + EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. + SelfDispatch, // Dispatch the events inside this thread. + }; + + EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, + OperatingMode mode) + : m_fd(wl_display_get_fd(wl)) + , m_pipefd{ -1, -1 } + , m_wldisplay(wl) + , m_wlevqueue(ev_queue) + , m_mode(mode) + , m_reading(true) + , m_quitting(false) + { + setObjectName(QStringLiteral("WaylandEventThread")); + } + + void readAndDispatchEvents() + { + /* + * Dispatch pending events and flush the requests at least once. If the event thread + * is not reading, try to call _prepare_read() to allow the event thread to poll(). + * If that fails, re-try dispatch & flush again until _prepare_read() is successful. + * + * This allow any call to readAndDispatchEvents() to start event thread's polling, + * not only the one issued from event thread's waitForReading(), which means functions + * called from dispatch_pending() can safely spin an event loop. + */ + for (;;) { + if (dispatchQueuePending() < 0) { + checkWaylandError(m_wldisplay); + return; + } + + wl_display_flush(m_wldisplay); + + // We have to check if event thread is reading every time we dispatch + // something, as that may recursively call this function. + if (m_reading.loadAcquire()) + break; + + if (prepareReadQueue() == 0) { + QMutexLocker l(&m_mutex); + m_reading.storeRelease(true); + m_cond.wakeOne(); + break; + } + } + } + + void stop() + { + // We have to both write to the pipe and set the flag, as the thread may be + // either in the poll() or waiting for _prepare_read(). + if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + { + QMutexLocker l(&m_mutex); + m_quitting = true; + m_cond.wakeOne(); + } + + wait(); + } + +Q_SIGNALS: + void needReadAndDispatch(); + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (qt_safe_pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the + // outbound ones. Wait until it's done before proceeding, unless we're told to quit. + while (waitForReading()) { + pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } }; + poll(fds, 2, -1); + + if (fds[1].revents & POLLIN) { + // we don't really care to read the byte that was written here since we're closing down + wl_display_cancel_read(m_wldisplay); + break; + } + + if (fds[0].revents & POLLIN) + wl_display_read_events(m_wldisplay); + // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop + // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which + // case we don't care anymore about them. + else + wl_display_cancel_read(m_wldisplay); + } + } + +private: + bool waitForReading() + { + Q_ASSERT(QThread::currentThread() == this); + + m_reading.storeRelease(false); + + if (m_mode == SelfDispatch) { + readAndDispatchEvents(); + } else { + Q_EMIT needReadAndDispatch(); + + QMutexLocker lock(&m_mutex); + // m_reading might be set from our emit or some other invocation of + // readAndDispatchEvents(). + while (!m_reading.loadRelaxed() && !m_quitting) + m_cond.wait(&m_mutex); + } + + return !m_quitting; + } + + int dispatchQueuePending() + { + if (m_wlevqueue) + return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); + else + return wl_display_dispatch_pending(m_wldisplay); + } + + int prepareReadQueue() + { + if (m_wlevqueue) + return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); + else + return wl_display_prepare_read(m_wldisplay); + } + + int m_fd; + int m_pipefd[2]; + wl_display *m_wldisplay; + wl_event_queue *m_wlevqueue; + OperatingMode m_mode; + + /* Concurrency note when operating in EmitToDispatch mode: + * m_reading is set to false inside event thread's waitForReading(), and is + * set to true inside main thread's readAndDispatchEvents(). + * The lock is not taken when setting m_reading to false, as the main thread + * is not actively waiting for it to turn false. However, the lock is taken + * inside readAndDispatchEvents() before setting m_reading to true, + * as the event thread is actively waiting for it under the wait condition. + */ + + QAtomicInteger m_reading; + bool m_quitting; + QMutex m_mutex; + QWaitCondition m_cond; +}; + Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging struct wl_surface *QWaylandDisplay::createSurface(void *handle) @@ -167,6 +358,12 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) QWaylandDisplay::~QWaylandDisplay(void) { + if (m_eventThread) + m_eventThread->stop(); + + if (m_frameEventQueueThread) + m_frameEventQueueThread->stop(); + if (mSyncCallback) wl_callback_destroy(mSyncCallback); @@ -211,100 +408,34 @@ void QWaylandDisplay::ensureScreen() Q_ASSERT(!QGuiApplication::screens().empty()); } -void QWaylandDisplay::checkError() const -{ - int ecode = wl_display_get_error(mDisplay); - if ((ecode == EPIPE || ecode == ECONNRESET)) { - // special case this to provide a nicer error - qWarning("The Wayland connection broke. Did the Wayland compositor die?"); - } else { - qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); - } - _exit(1); -} - +// Called in main thread, either from queued signal or directly. void QWaylandDisplay::flushRequests() { - if (wl_display_prepare_read(mDisplay) == 0) { - wl_display_read_events(mDisplay); - } + m_eventThread->readAndDispatchEvents(); +} - if (wl_display_dispatch_pending(mDisplay) < 0) - checkError(); +// We have to wait until we have an eventDispatcher before creating the eventThread, +// otherwise forceRoundTrip() may block inside _events_read() because eventThread is +// polling. +void QWaylandDisplay::initEventThread() +{ + m_eventThread.reset( + new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); + connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, + &QWaylandDisplay::flushRequests, Qt::QueuedConnection); + m_eventThread->start(); - { - QReadLocker locker(&m_frameQueueLock); - for (const FrameQueue &q : mExternalQueues) { - QMutexLocker locker(q.mutex); - while (wl_display_prepare_read_queue(mDisplay, q.queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, q.queue); - wl_display_read_events(mDisplay); - wl_display_dispatch_queue_pending(mDisplay, q.queue); - } - } - - wl_display_flush(mDisplay); + // wl_display_disconnect() free this. + m_frameEventQueue = wl_display_create_queue(mDisplay); + m_frameEventQueueThread.reset( + new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch)); + m_frameEventQueueThread->start(); } void QWaylandDisplay::blockingReadEvents() { if (wl_display_dispatch(mDisplay) < 0) - checkError(); -} - -void QWaylandDisplay::destroyFrameQueue(const QWaylandDisplay::FrameQueue &q) -{ - QWriteLocker locker(&m_frameQueueLock); - auto it = std::find_if(mExternalQueues.begin(), - mExternalQueues.end(), - [&q] (const QWaylandDisplay::FrameQueue &other){ return other.queue == q.queue; }); - Q_ASSERT(it != mExternalQueues.end()); - mExternalQueues.erase(it); - if (q.queue != nullptr) - wl_event_queue_destroy(q.queue); - delete q.mutex; -} - -QWaylandDisplay::FrameQueue QWaylandDisplay::createFrameQueue() -{ - QWriteLocker locker(&m_frameQueueLock); - FrameQueue q{createEventQueue()}; - mExternalQueues.append(q); - return q; -} - -wl_event_queue *QWaylandDisplay::createEventQueue() -{ - return wl_display_create_queue(mDisplay); -} - -void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout) -{ - if (!condition()) - return; - - QElapsedTimer timer; - timer.start(); - struct pollfd pFd = qt_make_pollfd(wl_display_get_fd(mDisplay), POLLIN); - while (timeout == -1 || timer.elapsed() < timeout) { - while (wl_display_prepare_read_queue(mDisplay, queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, queue); - - wl_display_flush(mDisplay); - - const int remaining = qMax(timeout - timer.elapsed(), 0ll); - const int pollTimeout = timeout == -1 ? -1 : remaining; - if (qt_poll_msecs(&pFd, 1, pollTimeout) > 0) - wl_display_read_events(mDisplay); - else - wl_display_cancel_read(mDisplay); - - if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0) - checkError(); - - if (!condition()) - break; - } + checkWaylandError(mDisplay); } void QWaylandDisplay::checkTextInputProtocol() @@ -748,4 +879,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p } // namespace QtWaylandClient +#include "qwaylanddisplay.moc" + QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index fc928ff3bd5..6d655a66b23 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -111,6 +111,7 @@ class QWaylandSurface; class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; +class EventThread; typedef void (*RegistryListener)(void *data, struct wl_registry *registry, @@ -122,12 +123,6 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland Q_OBJECT public: - struct FrameQueue { - FrameQueue(wl_event_queue *q = nullptr) : queue(q), mutex(new QMutex) {} - wl_event_queue *queue; - QMutex *mutex; - }; - QWaylandDisplay(QWaylandIntegration *waylandIntegration); ~QWaylandDisplay(void) override; @@ -215,20 +210,16 @@ public: void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); void handleWindowDestroyed(QWaylandWindow *window); - wl_event_queue *createEventQueue(); - FrameQueue createFrameQueue(); - void destroyFrameQueue(const FrameQueue &q); - void dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout = -1); + wl_event_queue *frameEventQueue() { return m_frameEventQueue; }; bool isKeyboardAvailable() const; + void initEventThread(); public slots: void blockingReadEvents(); void flushRequests(); private: - void checkError() const; - void handleWaylandSync(); void requestWaylandSync(); @@ -246,6 +237,9 @@ private: }; struct wl_display *mDisplay = nullptr; + QScopedPointer m_eventThread; + wl_event_queue *m_frameEventQueue = nullptr; + QScopedPointer m_frameEventQueueThread; QtWayland::wl_compositor mCompositor; QScopedPointer mShm; QList mWaitingScreens; @@ -298,10 +292,8 @@ private: QPointer mLastInputWindow; QPointer mLastKeyboardFocus; QList mActiveWindows; - QList mExternalQueues; struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; - QReadWriteLock m_frameQueueLock; bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index d54dff5ce56..5b712cab970 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -208,11 +208,9 @@ void QWaylandIntegration::initializePlatform() void QWaylandIntegration::initialize() { - int fd = wl_display_get_fd(mDisplay->wl_display()); - QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data()); - QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests())); + mDisplay->initEventThread(); - // Call this after eventDispatcher is connected with QSocketNotifier for QWaylandDisplay::forceRoundTrip() + // Call this after initializing event thread for QWaylandDisplay::forceRoundTrip() initializePlatform(); // But the aboutToBlock() and awake() should be connected after initializePlatform(). diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index 7f4088aa0a8..61537c166d7 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -76,7 +76,6 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) : QPlatformWindow(window) , mDisplay(display) - , mFrameQueue(mDisplay->createFrameQueue()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { { @@ -95,8 +94,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) QWaylandWindow::~QWaylandWindow() { - mDisplay->destroyFrameQueue(mFrameQueue); - delete mWindowDecoration; if (mSurface) @@ -673,6 +670,8 @@ const wl_callback_listener QWaylandWindow::callbackListener = { void QWaylandWindow::handleFrameCallback() { + QMutexLocker locker(&mFrameSyncMutex); + mWaitingForFrameCallback = false; mFrameCallbackElapsedTimer.invalidate(); @@ -683,6 +682,8 @@ void QWaylandWindow::handleFrameCallback() mWaitingForUpdateDelivery = true; QMetaObject::invokeMethod(this, &QWaylandWindow::doHandleFrameCallback, Qt::QueuedConnection); } + + mFrameSyncWait.notify_all(); } void QWaylandWindow::doHandleFrameCallback() @@ -699,8 +700,10 @@ void QWaylandWindow::doHandleFrameCallback() bool QWaylandWindow::waitForFrameSync(int timeout) { - QMutexLocker locker(mFrameQueue.mutex); - mDisplay->dispatchQueueWhile(mFrameQueue.queue, [&]() { return mWaitingForFrameCallback; }, timeout); + QMutexLocker locker(&mFrameSyncMutex); + + QDeadlineTimer deadline(timeout); + while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { } if (mWaitingForFrameCallback) { qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; @@ -1340,8 +1343,11 @@ void QWaylandWindow::requestUpdate() Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA // If we have a frame callback all is good and will be taken care of there - if (mWaitingForFrameCallback) - return; + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log @@ -1354,7 +1360,12 @@ void QWaylandWindow::requestUpdate() // so use invokeMethod to delay the delivery a bit. QMetaObject::invokeMethod(this, [this] { // Things might have changed in the meantime - if (hasPendingUpdateRequest() && !mWaitingForFrameCallback) + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) deliverUpdateRequest(); }, Qt::QueuedConnection); } @@ -1374,9 +1385,10 @@ void QWaylandWindow::handleUpdate() if (!mSurface) return; - QMutexLocker locker(mFrameQueue.mutex); + QMutexLocker locker(&mFrameSyncMutex); + struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); - wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mFrameQueue.queue); + wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); mFrameCallback = wl_surface_frame(wrappedSurface); wl_proxy_wrapper_destroy(wrappedSurface); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); @@ -1386,6 +1398,8 @@ void QWaylandWindow::handleUpdate() // Start a timer for handling the case when the compositor stops sending frame callbacks. if (mFrameCallbackTimeout > 0) { QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) { if (mFrameCallbackCheckIntervalTimerId < 0) mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index a041dc40fae..17ed9fdca13 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -274,7 +274,7 @@ protected: int mFrameCallbackCheckIntervalTimerId = -1; QElapsedTimer mFrameCallbackElapsedTimer; struct ::wl_callback *mFrameCallback = nullptr; - QWaylandDisplay::FrameQueue mFrameQueue; + QMutex mFrameSyncMutex; QWaitCondition mFrameSyncWait; // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer diff --git a/tests/auto/wayland/CMakeLists.txt b/tests/auto/wayland/CMakeLists.txt index ea6c4c1b215..167cc6070c5 100644 --- a/tests/auto/wayland/CMakeLists.txt +++ b/tests/auto/wayland/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(client) add_subdirectory(datadevicev1) add_subdirectory(fullscreenshellv1) add_subdirectory(iviapplication) +add_subdirectory(multithreaded) add_subdirectory(nooutput) add_subdirectory(output) add_subdirectory(primaryselectionv1) diff --git a/tests/auto/wayland/multithreaded/CMakeLists.txt b/tests/auto/wayland/multithreaded/CMakeLists.txt new file mode 100644 index 00000000000..49d93297f5b --- /dev/null +++ b/tests/auto/wayland/multithreaded/CMakeLists.txt @@ -0,0 +1,12 @@ +# Generated from multithreaded.pro. + +##################################################################### +## tst_multithreaded Test: +##################################################################### + +qt_internal_add_test(tst_multithreaded + SOURCES + tst_multithreaded.cpp + PUBLIC_LIBRARIES + SharedClientTest +) diff --git a/tests/auto/wayland/multithreaded/tst_multithreaded.cpp b/tests/auto/wayland/multithreaded/tst_multithreaded.cpp new file mode 100644 index 00000000000..ce88a72fd53 --- /dev/null +++ b/tests/auto/wayland/multithreaded/tst_multithreaded.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2020 UBports Foundataion. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +#include +#include +#include + +#include "mockcompositor.h" + +using namespace MockCompositor; + +/* + * This class simulate a thread from another library which use poll() to wait + * for data from Wayland compositor. + */ + +class ExternalWaylandReaderThread : public QThread +{ +public: + ExternalWaylandReaderThread(struct wl_display *disp) + : QThread() + , m_disp(disp) + { + setObjectName(QStringLiteral("ExternalWaylandReader")); + } + + ~ExternalWaylandReaderThread() + { + if (m_pipefd[1] != -1 && write(m_pipefd[1], "q", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + wait(); + } + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (::pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + struct wl_event_queue *a_queue = wl_display_create_queue(m_disp); + struct pollfd fds[2] = { { wl_display_get_fd(m_disp), POLLIN, 0 }, + { m_pipefd[0], POLLIN, 0 } }; + + while (true) { + // No wl_proxy is assigned to this queue, thus guaranteed to be always empty. + Q_ASSERT(wl_display_prepare_read_queue(m_disp, a_queue) == 0); + wl_display_flush(m_disp); + + // Wakeup every 10 seconds so that if Qt blocks in _read_events(), + // it won't last forever. + poll(fds, /* nfds */ 2, 10000); + + if (fds[0].revents & POLLIN) { + wl_display_read_events(m_disp); + } else { + wl_display_cancel_read(m_disp); + } + + if (fds[1].revents & POLLIN) { + char pipeIn; + read(m_pipefd[0], &pipeIn, 1); + if (pipeIn == 'q') + break; + } + } + + wl_event_queue_destroy(a_queue); + } + +private: + struct wl_display *m_disp; + int m_pipefd[2] = { -1, -1 }; +}; + +class tst_multithreaded : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void initTestCase() + { + m_config.autoConfigure = true; + m_config.autoEnter = false; + } + void init() + { + // a test case is given new simulated thread. + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + struct wl_display *wl_dpy = + (struct wl_display *)native->nativeResourceForWindow("display", NULL); + + m_extThread.reset(new ExternalWaylandReaderThread(wl_dpy)); + m_extThread->start(); + } + void cleanup() + { + QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); + } + + void mainThreadIsNotBlocked(); + +public: + QScopedPointer m_extThread; +}; + +void tst_multithreaded::mainThreadIsNotBlocked() +{ + QElapsedTimer timer; + timer.start(); + + QTest::qWait(100); + QVERIFY(timer.elapsed() < 200); +} + +QCOMPOSITOR_TEST_MAIN(tst_multithreaded) +#include "tst_multithreaded.moc"