Blame SOURCES/0030-Move-the-wayland-socket-polling-to-a-separate-event-.patch

165ad0
From 91d36a2497f3289996d788c8974583bccac3c842 Mon Sep 17 00:00:00 2001
165ad0
From: Adrien Faveraux <af@brain-networks.fr>
165ad0
Date: Fri, 26 Nov 2021 09:18:58 +0100
165ad0
Subject: [PATCH 30/40] Move the wayland socket polling to a separate event
165ad0
 thread
165ad0
165ad0
New event threads is introduced which calls poll() on the wayland fd,
165ad0
instead of relying on the event dispatcher by using the QSocketNotifier.
165ad0
This allows to call in the proper order the wl_display_prepare_read(),
165ad0
poll() and wl_display_read_events() functions.
165ad0
165ad0
One thread is responsible for the default queue; when needed, it emit
165ad0
a signal so that the main thread can dispatch the queue. Another thread
165ad0
is responsible for the dedicated queue for frame callbacks; this thread
165ad0
will dispatch events on the thread itself.
165ad0
165ad0
QWaylandWindow is updated to, instead of each window's dedicated event
165ad0
queue, use this queue for frame callbacks.
165ad0
165ad0
Co-authored-by: Ratchanan Srirattanamet <ratchanan@ubports.com>
165ad0
Task-number: QTBUG-66075
165ad0
Change-Id: Ibb33ad7f4193b866d1b8d7a0405a94d59dcad5eb
165ad0
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
165ad0
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
165ad0
(cherry picked from commit 92a7904d9651348b0c307e84251c8440c6f75b22)
165ad0
---
165ad0
 src/client/qwaylanddisplay.cpp     | 302 +++++++++++++++++++++--------
165ad0
 src/client/qwaylanddisplay_p.h     |  21 +-
165ad0
 src/client/qwaylandintegration.cpp |   4 +-
165ad0
 src/client/qwaylandwindow.cpp      |  34 +++-
165ad0
 src/client/qwaylandwindow_p.h      |   2 +-
165ad0
 5 files changed, 255 insertions(+), 108 deletions(-)
165ad0
165ad0
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
165ad0
index 0f75cb7e..a7ce280a 100644
165ad0
--- a/src/client/qwaylanddisplay.cpp
165ad0
+++ b/src/client/qwaylanddisplay.cpp
165ad0
@@ -85,10 +85,203 @@
165ad0
 
165ad0
 #include <errno.h>
165ad0
 
165ad0
+#include <tuple> // for std::tie
165ad0
+
165ad0
+static void checkWaylandError(struct wl_display *display)
165ad0
+{
165ad0
+    int ecode = wl_display_get_error(display);
165ad0
+    if ((ecode == EPIPE || ecode == ECONNRESET)) {
165ad0
+        // special case this to provide a nicer error
165ad0
+        qWarning("The Wayland connection broke. Did the Wayland compositor die?");
165ad0
+    } else {
165ad0
+        qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode));
165ad0
+    }
165ad0
+    _exit(1);
165ad0
+}
165ad0
+
165ad0
 QT_BEGIN_NAMESPACE
165ad0
 
165ad0
 namespace QtWaylandClient {
165ad0
 
165ad0
+class EventThread : public QThread
165ad0
+{
165ad0
+    Q_OBJECT
165ad0
+public:
165ad0
+    enum OperatingMode {
165ad0
+        EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread.
165ad0
+        SelfDispatch, // Dispatch the events inside this thread.
165ad0
+    };
165ad0
+
165ad0
+    EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue,
165ad0
+                OperatingMode mode)
165ad0
+        : m_fd(wl_display_get_fd(wl))
165ad0
+        , m_pipefd{ -1, -1 }
165ad0
+        , m_wldisplay(wl)
165ad0
+        , m_wlevqueue(ev_queue)
165ad0
+        , m_mode(mode)
165ad0
+        , m_reading(true)
165ad0
+        , m_quitting(false)
165ad0
+    {
165ad0
+        setObjectName(QStringLiteral("WaylandEventThread"));
165ad0
+    }
165ad0
+
165ad0
+    void readAndDispatchEvents()
165ad0
+    {
165ad0
+        /*
165ad0
+         * Dispatch pending events and flush the requests at least once. If the event thread
165ad0
+         * is not reading, try to call _prepare_read() to allow the event thread to poll().
165ad0
+         * If that fails, re-try dispatch & flush again until _prepare_read() is successful.
165ad0
+         *
165ad0
+         * This allow any call to readAndDispatchEvents() to start event thread's polling,
165ad0
+         * not only the one issued from event thread's waitForReading(), which means functions
165ad0
+         * called from dispatch_pending() can safely spin an event loop.
165ad0
+         */
165ad0
+        for (;;) {
165ad0
+            if (dispatchQueuePending() < 0) {
165ad0
+                checkWaylandError(m_wldisplay);
165ad0
+                return;
165ad0
+            }
165ad0
+
165ad0
+            wl_display_flush(m_wldisplay);
165ad0
+
165ad0
+            // We have to check if event thread is reading every time we dispatch
165ad0
+            // something, as that may recursively call this function.
165ad0
+            if (m_reading.loadAcquire())
165ad0
+                break;
165ad0
+
165ad0
+            if (prepareReadQueue() == 0) {
165ad0
+                QMutexLocker l(&m_mutex);
165ad0
+                m_reading.storeRelease(true);
165ad0
+                m_cond.wakeOne();
165ad0
+                break;
165ad0
+            }
165ad0
+        }
165ad0
+    }
165ad0
+
165ad0
+    void stop()
165ad0
+    {
165ad0
+        // We have to both write to the pipe and set the flag, as the thread may be
165ad0
+        // either in the poll() or waiting for _prepare_read().
165ad0
+        if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1)
165ad0
+            qWarning("Failed to write to the pipe: %s.", strerror(errno));
165ad0
+
165ad0
+        {
165ad0
+            QMutexLocker l(&m_mutex);
165ad0
+            m_quitting = true;
165ad0
+            m_cond.wakeOne();
165ad0
+        }
165ad0
+
165ad0
+        wait();
165ad0
+    }
165ad0
+
165ad0
+Q_SIGNALS:
165ad0
+    void needReadAndDispatch();
165ad0
+
165ad0
+protected:
165ad0
+    void run() override
165ad0
+    {
165ad0
+        // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets
165ad0
+        // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore.
165ad0
+        struct Pipe
165ad0
+        {
165ad0
+            Pipe(int *fds)
165ad0
+                : fds(fds)
165ad0
+            {
165ad0
+                if (qt_safe_pipe(fds) != 0)
165ad0
+                    qWarning("Pipe creation failed. Quitting may hang.");
165ad0
+            }
165ad0
+            ~Pipe()
165ad0
+            {
165ad0
+                if (fds[0] != -1) {
165ad0
+                    close(fds[0]);
165ad0
+                    close(fds[1]);
165ad0
+                }
165ad0
+            }
165ad0
+
165ad0
+            int *fds;
165ad0
+        } pipe(m_pipefd);
165ad0
+
165ad0
+        // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the
165ad0
+        // outbound ones. Wait until it's done before proceeding, unless we're told to quit.
165ad0
+        while (waitForReading()) {
165ad0
+            pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } };
165ad0
+            poll(fds, 2, -1);
165ad0
+
165ad0
+            if (fds[1].revents & POLLIN) {
165ad0
+                // we don't really care to read the byte that was written here since we're closing down
165ad0
+                wl_display_cancel_read(m_wldisplay);
165ad0
+                break;
165ad0
+            }
165ad0
+
165ad0
+            if (fds[0].revents & POLLIN)
165ad0
+                wl_display_read_events(m_wldisplay);
165ad0
+                // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop
165ad0
+                // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which
165ad0
+                // case we don't care anymore about them.
165ad0
+            else
165ad0
+                wl_display_cancel_read(m_wldisplay);
165ad0
+        }
165ad0
+    }
165ad0
+
165ad0
+private:
165ad0
+    bool waitForReading()
165ad0
+    {
165ad0
+        Q_ASSERT(QThread::currentThread() == this);
165ad0
+
165ad0
+        m_reading.storeRelease(false);
165ad0
+
165ad0
+        if (m_mode == SelfDispatch) {
165ad0
+            readAndDispatchEvents();
165ad0
+        } else {
165ad0
+            Q_EMIT needReadAndDispatch();
165ad0
+
165ad0
+            QMutexLocker lock(&m_mutex);
165ad0
+            // m_reading might be set from our emit or some other invocation of
165ad0
+            // readAndDispatchEvents().
165ad0
+            while (!m_reading.loadRelaxed() && !m_quitting)
165ad0
+                m_cond.wait(&m_mutex);
165ad0
+        }
165ad0
+
165ad0
+        return !m_quitting;
165ad0
+    }
165ad0
+
165ad0
+    int dispatchQueuePending()
165ad0
+    {
165ad0
+        if (m_wlevqueue)
165ad0
+            return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue);
165ad0
+        else
165ad0
+            return wl_display_dispatch_pending(m_wldisplay);
165ad0
+    }
165ad0
+
165ad0
+    int prepareReadQueue()
165ad0
+    {
165ad0
+        if (m_wlevqueue)
165ad0
+            return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue);
165ad0
+        else
165ad0
+            return wl_display_prepare_read(m_wldisplay);
165ad0
+    }
165ad0
+
165ad0
+    int m_fd;
165ad0
+    int m_pipefd[2];
165ad0
+    wl_display *m_wldisplay;
165ad0
+    wl_event_queue *m_wlevqueue;
165ad0
+    OperatingMode m_mode;
165ad0
+
165ad0
+    /* Concurrency note when operating in EmitToDispatch mode:
165ad0
+     * m_reading is set to false inside event thread's waitForReading(), and is
165ad0
+     * set to true inside main thread's readAndDispatchEvents().
165ad0
+     * The lock is not taken when setting m_reading to false, as the main thread
165ad0
+     * is not actively waiting for it to turn false. However, the lock is taken
165ad0
+     * inside readAndDispatchEvents() before setting m_reading to true,
165ad0
+     * as the event thread is actively waiting for it under the wait condition.
165ad0
+     */
165ad0
+
165ad0
+    QAtomicInteger<bool> m_reading;
165ad0
+    bool m_quitting;
165ad0
+    QMutex m_mutex;
165ad0
+    QWaitCondition m_cond;
165ad0
+};
165ad0
+
165ad0
 Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging
165ad0
 
165ad0
 struct wl_surface *QWaylandDisplay::createSurface(void *handle)
165ad0
@@ -162,6 +355,12 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration)
165ad0
 
165ad0
 QWaylandDisplay::~QWaylandDisplay(void)
165ad0
 {
165ad0
+    if (m_eventThread)
165ad0
+        m_eventThread->stop();
165ad0
+
165ad0
+    if (m_frameEventQueueThread)
165ad0
+        m_frameEventQueueThread->stop();
165ad0
+
165ad0
     if (mSyncCallback)
165ad0
         wl_callback_destroy(mSyncCallback);
165ad0
 
165ad0
@@ -208,98 +407,37 @@ void QWaylandDisplay::ensureScreen()
165ad0
 
165ad0
 void QWaylandDisplay::checkError() const
165ad0
 {
165ad0
-    int ecode = wl_display_get_error(mDisplay);
165ad0
-    if ((ecode == EPIPE || ecode == ECONNRESET)) {
165ad0
-        // special case this to provide a nicer error
165ad0
-        qWarning("The Wayland connection broke. Did the Wayland compositor die?");
165ad0
-    } else {
165ad0
-        qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode));
165ad0
-    }
165ad0
-    _exit(1);
165ad0
+    checkWaylandError(mDisplay);
165ad0
 }
165ad0
 
165ad0
+// Called in main thread, either from queued signal or directly.
165ad0
 void QWaylandDisplay::flushRequests()
165ad0
 {
165ad0
-    if (wl_display_prepare_read(mDisplay) == 0) {
165ad0
-        wl_display_read_events(mDisplay);
165ad0
-    }
165ad0
-
165ad0
-    if (wl_display_dispatch_pending(mDisplay) < 0)
165ad0
-        checkError();
165ad0
-
165ad0
-    {
165ad0
-        QReadLocker locker(&m_frameQueueLock);
165ad0
-        for (const FrameQueue &q : mExternalQueues) {
165ad0
-            QMutexLocker locker(q.mutex);
165ad0
-            while (wl_display_prepare_read_queue(mDisplay, q.queue) != 0)
165ad0
-                wl_display_dispatch_queue_pending(mDisplay, q.queue);
165ad0
-            wl_display_read_events(mDisplay);
165ad0
-            wl_display_dispatch_queue_pending(mDisplay, q.queue);
165ad0
-        }
165ad0
-    }
165ad0
-
165ad0
-    wl_display_flush(mDisplay);
165ad0
-}
165ad0
-
165ad0
-void QWaylandDisplay::blockingReadEvents()
165ad0
-{
165ad0
-    if (wl_display_dispatch(mDisplay) < 0)
165ad0
-        checkError();
165ad0
-}
165ad0
-
165ad0
-void QWaylandDisplay::destroyFrameQueue(const QWaylandDisplay::FrameQueue &q)
165ad0
-{
165ad0
-    QWriteLocker locker(&m_frameQueueLock);
165ad0
-    auto it = std::find_if(mExternalQueues.begin(),
165ad0
-                           mExternalQueues.end(),
165ad0
-                           [&q] (const QWaylandDisplay::FrameQueue &other){ return other.queue == q.queue; });
165ad0
-    Q_ASSERT(it != mExternalQueues.end());
165ad0
-    mExternalQueues.erase(it);
165ad0
-    if (q.queue != nullptr)
165ad0
-        wl_event_queue_destroy(q.queue);
165ad0
-    delete q.mutex;
165ad0
+    m_eventThread->readAndDispatchEvents();
165ad0
 }
165ad0
 
165ad0
-QWaylandDisplay::FrameQueue QWaylandDisplay::createFrameQueue()
165ad0
+// We have to wait until we have an eventDispatcher before creating the eventThread,
165ad0
+// otherwise forceRoundTrip() may block inside _events_read() because eventThread is
165ad0
+// polling.
165ad0
+void QWaylandDisplay::initEventThread()
165ad0
 {
165ad0
-    QWriteLocker locker(&m_frameQueueLock);
165ad0
-    FrameQueue q{createEventQueue()};
165ad0
-    mExternalQueues.append(q);
165ad0
-    return q;
165ad0
-}
165ad0
+    m_eventThread.reset(
165ad0
+            new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch));
165ad0
+    connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this,
165ad0
+            &QWaylandDisplay::flushRequests, Qt::QueuedConnection);
165ad0
+    m_eventThread->start();
165ad0
 
165ad0
-wl_event_queue *QWaylandDisplay::createEventQueue()
165ad0
-{
165ad0
-    return wl_display_create_queue(mDisplay);
165ad0
+    // wl_display_disconnect() free this.
165ad0
+    m_frameEventQueue = wl_display_create_queue(mDisplay);
165ad0
+    m_frameEventQueueThread.reset(
165ad0
+            new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch));
165ad0
+    m_frameEventQueueThread->start();
165ad0
 }
165ad0
 
165ad0
-void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function<bool ()> condition, int timeout)
165ad0
+void QWaylandDisplay::blockingReadEvents()
165ad0
 {
165ad0
-    if (!condition())
165ad0
-        return;
165ad0
-
165ad0
-    QElapsedTimer timer;
165ad0
-    timer.start();
165ad0
-    struct pollfd pFd = qt_make_pollfd(wl_display_get_fd(mDisplay), POLLIN);
165ad0
-    while (timeout == -1 || timer.elapsed() < timeout) {
165ad0
-        while (wl_display_prepare_read_queue(mDisplay, queue) != 0)
165ad0
-            wl_display_dispatch_queue_pending(mDisplay, queue);
165ad0
-
165ad0
-        wl_display_flush(mDisplay);
165ad0
-
165ad0
-        const int remaining = qMax(timeout - timer.elapsed(), 0ll);
165ad0
-        const int pollTimeout = timeout == -1 ? -1 : remaining;
165ad0
-        if (qt_poll_msecs(&pFd, 1, pollTimeout) > 0)
165ad0
-            wl_display_read_events(mDisplay);
165ad0
-        else
165ad0
-            wl_display_cancel_read(mDisplay);
165ad0
-
165ad0
-        if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0)
165ad0
-            checkError();
165ad0
-
165ad0
-        if (!condition())
165ad0
-            break;
165ad0
-    }
165ad0
+    if (wl_display_dispatch(mDisplay) < 0)
165ad0
+        checkWaylandError(mDisplay);
165ad0
 }
165ad0
 
165ad0
 QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const
165ad0
@@ -674,4 +812,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p
165ad0
 
165ad0
 } // namespace QtWaylandClient
165ad0
 
165ad0
+#include "qwaylanddisplay.moc"
165ad0
+
165ad0
 QT_END_NAMESPACE
165ad0
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
165ad0
index d9c8849f..42bc661d 100644
165ad0
--- a/src/client/qwaylanddisplay_p.h
165ad0
+++ b/src/client/qwaylanddisplay_p.h
165ad0
@@ -109,6 +109,7 @@ class QWaylandSurface;
165ad0
 class QWaylandShellIntegration;
165ad0
 class QWaylandCursor;
165ad0
 class QWaylandCursorTheme;
165ad0
+class EventThread;
165ad0
 
165ad0
 typedef void (*RegistryListener)(void *data,
165ad0
                                  struct wl_registry *registry,
165ad0
@@ -120,12 +121,6 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland
165ad0
     Q_OBJECT
165ad0
 
165ad0
 public:
165ad0
-    struct FrameQueue {
165ad0
-        FrameQueue(wl_event_queue *q = nullptr) : queue(q), mutex(new QMutex) {}
165ad0
-        wl_event_queue *queue;
165ad0
-        QMutex *mutex;
165ad0
-    };
165ad0
-
165ad0
     QWaylandDisplay(QWaylandIntegration *waylandIntegration);
165ad0
     ~QWaylandDisplay(void) override;
165ad0
 
165ad0
@@ -212,12 +207,11 @@ public:
165ad0
     void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice);
165ad0
     void handleWindowDestroyed(QWaylandWindow *window);
165ad0
 
165ad0
-    wl_event_queue *createEventQueue();
165ad0
-    FrameQueue createFrameQueue();
165ad0
-    void destroyFrameQueue(const FrameQueue &q);
165ad0
-    void dispatchQueueWhile(wl_event_queue *queue, std::function<bool()> condition, int timeout = -1);
165ad0
+    wl_event_queue *frameEventQueue() { return m_frameEventQueue; };
165ad0
 
165ad0
     bool isKeyboardAvailable() const;
165ad0
+
165ad0
+    void initEventThread();
165ad0
 public slots:
165ad0
     void blockingReadEvents();
165ad0
     void flushRequests();
165ad0
@@ -240,6 +234,9 @@ private:
165ad0
     };
165ad0
 
165ad0
     struct wl_display *mDisplay = nullptr;
165ad0
+    QScopedPointer<EventThread> m_eventThread;
165ad0
+    wl_event_queue *m_frameEventQueue = nullptr;
165ad0
+    QScopedPointer<EventThread> m_frameEventQueueThread;
165ad0
     QtWayland::wl_compositor mCompositor;
165ad0
     QScopedPointer<QWaylandShm> mShm;
165ad0
     QList<QWaylandScreen *> mWaitingScreens;
165ad0
@@ -276,11 +273,9 @@ private:
165ad0
     QWaylandInputDevice *mLastInputDevice = nullptr;
165ad0
     QPointer<QWaylandWindow> mLastInputWindow;
165ad0
     QPointer<QWaylandWindow> mLastKeyboardFocus;
165ad0
-    QVector<QWaylandWindow *> mActiveWindows;
165ad0
-    QVector<FrameQueue> mExternalQueues;
165ad0
+    QList<QWaylandWindow *> mActiveWindows;
165ad0
     struct wl_callback *mSyncCallback = nullptr;
165ad0
     static const wl_callback_listener syncCallbackListener;
165ad0
-    QReadWriteLock m_frameQueueLock;
165ad0
 
165ad0
     bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull();
165ad0
 
165ad0
diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp
165ad0
index 3a6fa651..3b876047 100644
165ad0
--- a/src/client/qwaylandintegration.cpp
165ad0
+++ b/src/client/qwaylandintegration.cpp
165ad0
@@ -192,9 +192,7 @@ QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const
165ad0
 
165ad0
 void QWaylandIntegration::initialize()
165ad0
 {
165ad0
-    int fd = wl_display_get_fd(mDisplay->wl_display());
165ad0
-    QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data());
165ad0
-    QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests()));
165ad0
+    mDisplay->initEventThread();
165ad0
 
165ad0
     // Call after eventDispatcher is fully connected, for QWaylandDisplay::forceRoundTrip()
165ad0
     mDisplay->initialize();
165ad0
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
165ad0
index 1597f67e..7de19a74 100644
165ad0
--- a/src/client/qwaylandwindow.cpp
165ad0
+++ b/src/client/qwaylandwindow.cpp
165ad0
@@ -76,7 +76,6 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr;
165ad0
 QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display)
165ad0
     : QPlatformWindow(window)
165ad0
     , mDisplay(display)
165ad0
-    , mFrameQueue(mDisplay->createFrameQueue())
165ad0
     , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP"))
165ad0
 {
165ad0
     {
165ad0
@@ -95,8 +94,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display)
165ad0
 
165ad0
 QWaylandWindow::~QWaylandWindow()
165ad0
 {
165ad0
-    mDisplay->destroyFrameQueue(mFrameQueue);
165ad0
-
165ad0
     delete mWindowDecoration;
165ad0
 
165ad0
     if (mSurface)
165ad0
@@ -635,6 +632,8 @@ const wl_callback_listener QWaylandWindow::callbackListener = {
165ad0
 
165ad0
 void QWaylandWindow::handleFrameCallback()
165ad0
 {
165ad0
+    QMutexLocker locker(&mFrameSyncMutex);
165ad0
+
165ad0
     mWaitingForFrameCallback = false;
165ad0
     mFrameCallbackElapsedTimer.invalidate();
165ad0
 
165ad0
@@ -656,12 +655,16 @@ void QWaylandWindow::handleFrameCallback()
165ad0
         mWaitingForUpdateDelivery = true;
165ad0
         QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection);
165ad0
     }
165ad0
+
165ad0
+    mFrameSyncWait.notify_all();
165ad0
 }
165ad0
 
165ad0
 bool QWaylandWindow::waitForFrameSync(int timeout)
165ad0
 {
165ad0
-    QMutexLocker locker(mFrameQueue.mutex);
165ad0
-    mDisplay->dispatchQueueWhile(mFrameQueue.queue, [&]() { return mWaitingForFrameCallback; }, timeout);
165ad0
+    QMutexLocker locker(&mFrameSyncMutex);
165ad0
+
165ad0
+    QDeadlineTimer deadline(timeout);
165ad0
+    while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { }
165ad0
 
165ad0
     if (mWaitingForFrameCallback) {
165ad0
         qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed";
165ad0
@@ -1157,8 +1160,11 @@ void QWaylandWindow::requestUpdate()
165ad0
     Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA
165ad0
 
165ad0
     // If we have a frame callback all is good and will be taken care of there
165ad0
-    if (mWaitingForFrameCallback)
165ad0
-        return;
165ad0
+    {
165ad0
+        QMutexLocker locker(&mFrameSyncMutex);
165ad0
+        if (mWaitingForFrameCallback)
165ad0
+            return;
165ad0
+    }
165ad0
 
165ad0
     // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet
165ad0
     // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log
165ad0
@@ -1171,7 +1177,12 @@ void QWaylandWindow::requestUpdate()
165ad0
     // so use invokeMethod to delay the delivery a bit.
165ad0
     QMetaObject::invokeMethod(this, [this] {
165ad0
         // Things might have changed in the meantime
165ad0
-        if (hasPendingUpdateRequest() && !mWaitingForFrameCallback)
165ad0
+        {
165ad0
+            QMutexLocker locker(&mFrameSyncMutex);
165ad0
+            if (mWaitingForFrameCallback)
165ad0
+                return;
165ad0
+        }
165ad0
+        if (hasPendingUpdateRequest())
165ad0
             deliverUpdateRequest();
165ad0
     }, Qt::QueuedConnection);
165ad0
 }
165ad0
@@ -1191,9 +1202,10 @@ void QWaylandWindow::handleUpdate()
165ad0
     if (!mSurface)
165ad0
         return;
165ad0
 
165ad0
-    QMutexLocker locker(mFrameQueue.mutex);
165ad0
+    QMutexLocker locker(&mFrameSyncMutex);
165ad0
+
165ad0
     struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object()));
165ad0
-    wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mFrameQueue.queue);
165ad0
+    wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mDisplay->frameEventQueue());
165ad0
     mFrameCallback = wl_surface_frame(wrappedSurface);
165ad0
     wl_proxy_wrapper_destroy(wrappedSurface);
165ad0
     wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this);
165ad0
@@ -1203,6 +1215,8 @@ void QWaylandWindow::handleUpdate()
165ad0
     // Start a timer for handling the case when the compositor stops sending frame callbacks.
165ad0
     if (mFrameCallbackTimeout > 0) {
165ad0
         QMetaObject::invokeMethod(this, [this] {
165ad0
+            QMutexLocker locker(&mFrameSyncMutex);
165ad0
+
165ad0
             if (mWaitingForFrameCallback) {
165ad0
                 if (mFrameCallbackCheckIntervalTimerId < 0)
165ad0
                     mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout);
165ad0
diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h
165ad0
index e0687962..d45980a8 100644
165ad0
--- a/src/client/qwaylandwindow_p.h
165ad0
+++ b/src/client/qwaylandwindow_p.h
165ad0
@@ -232,7 +232,7 @@ protected:
165ad0
     int mFrameCallbackCheckIntervalTimerId = -1;
165ad0
     QElapsedTimer mFrameCallbackElapsedTimer;
165ad0
     struct ::wl_callback *mFrameCallback = nullptr;
165ad0
-    QWaylandDisplay::FrameQueue mFrameQueue;
165ad0
+    QMutex mFrameSyncMutex;
165ad0
     QWaitCondition mFrameSyncWait;
165ad0
 
165ad0
     // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer
165ad0
-- 
165ad0
2.35.1
165ad0