Fix frame sync related to unprotected multithread access
There is a few crashes happens in real life that frame callback is double-free'd and hit an assertion in wayland-client. e.g. https://bugs.kde.org/show_bug.cgi?id=450003 This is due to the WaylandEventThread and calls to QWaylandWindow::reset may free and unset the mFrameCallback at the same time. mFrameSyncMutex should be used to protect such access. Pick-to: 6.4 Change-Id: Ie01d08d07a2f10f70606ed1935caac09cb4f0382 Reviewed-by: David Edmundson <davidedmundson@kde.org>
This commit is contained in:
parent
27e59681ac
commit
2b49cf04a6
@ -228,13 +228,16 @@ void QWaylandWindow::reset()
|
|||||||
mSurface.reset();
|
mSurface.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFrameCallback) {
|
{
|
||||||
wl_callback_destroy(mFrameCallback);
|
QMutexLocker lock(&mFrameSyncMutex);
|
||||||
mFrameCallback = nullptr;
|
if (mFrameCallback) {
|
||||||
}
|
wl_callback_destroy(mFrameCallback);
|
||||||
|
mFrameCallback = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
mFrameCallbackElapsedTimer.invalidate();
|
mFrameCallbackElapsedTimer.invalidate();
|
||||||
mWaitingForFrameCallback = false;
|
mWaitingForFrameCallback = false;
|
||||||
|
}
|
||||||
mFrameCallbackTimedOut = false;
|
mFrameCallbackTimedOut = false;
|
||||||
mWaitingToApplyConfigure = false;
|
mWaitingToApplyConfigure = false;
|
||||||
|
|
||||||
@ -672,18 +675,21 @@ const wl_callback_listener QWaylandWindow::callbackListener = {
|
|||||||
[](void *data, wl_callback *callback, uint32_t time) {
|
[](void *data, wl_callback *callback, uint32_t time) {
|
||||||
Q_UNUSED(time);
|
Q_UNUSED(time);
|
||||||
auto *window = static_cast<QWaylandWindow*>(data);
|
auto *window = static_cast<QWaylandWindow*>(data);
|
||||||
|
window->handleFrameCallback(callback);
|
||||||
Q_ASSERT(callback == window->mFrameCallback);
|
|
||||||
wl_callback_destroy(callback);
|
|
||||||
window->mFrameCallback = nullptr;
|
|
||||||
|
|
||||||
window->handleFrameCallback();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void QWaylandWindow::handleFrameCallback()
|
void QWaylandWindow::handleFrameCallback(wl_callback* callback)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&mFrameSyncMutex);
|
QMutexLocker locker(&mFrameSyncMutex);
|
||||||
|
if (!mFrameCallback) {
|
||||||
|
// This means the callback is already unset by QWaylandWindow::reset.
|
||||||
|
// The wl_callback object will be destroyed there too.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Q_ASSERT(callback == mFrameCallback);
|
||||||
|
wl_callback_destroy(callback);
|
||||||
|
mFrameCallback = nullptr;
|
||||||
|
|
||||||
mWaitingForFrameCallback = false;
|
mWaitingForFrameCallback = false;
|
||||||
mFrameCallbackElapsedTimer.invalidate();
|
mFrameCallbackElapsedTimer.invalidate();
|
||||||
@ -1382,19 +1388,24 @@ void QWaylandWindow::timerEvent(QTimerEvent *event)
|
|||||||
if (event->timerId() != mFrameCallbackCheckIntervalTimerId)
|
if (event->timerId() != mFrameCallbackCheckIntervalTimerId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout);
|
{
|
||||||
if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) {
|
QMutexLocker lock(&mFrameSyncMutex);
|
||||||
killTimer(mFrameCallbackCheckIntervalTimerId);
|
|
||||||
mFrameCallbackCheckIntervalTimerId = -1;
|
|
||||||
}
|
|
||||||
if (mFrameCallbackElapsedTimer.isValid() && callbackTimerExpired) {
|
|
||||||
mFrameCallbackElapsedTimer.invalidate();
|
|
||||||
|
|
||||||
qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed";
|
bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout);
|
||||||
mFrameCallbackTimedOut = true;
|
if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) {
|
||||||
mWaitingForUpdate = false;
|
killTimer(mFrameCallbackCheckIntervalTimerId);
|
||||||
sendExposeEvent(QRect());
|
mFrameCallbackCheckIntervalTimerId = -1;
|
||||||
|
}
|
||||||
|
if (!mFrameCallbackElapsedTimer.isValid() || !callbackTimerExpired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mFrameCallbackElapsedTimer.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed";
|
||||||
|
mFrameCallbackTimedOut = true;
|
||||||
|
mWaitingForUpdate = false;
|
||||||
|
sendExposeEvent(QRect());
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWaylandWindow::requestUpdate()
|
void QWaylandWindow::requestUpdate()
|
||||||
@ -1437,15 +1448,14 @@ void QWaylandWindow::handleUpdate()
|
|||||||
{
|
{
|
||||||
qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread();
|
qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread();
|
||||||
|
|
||||||
if (mWaitingForFrameCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: Should sync subsurfaces avoid requesting frame callbacks?
|
// TODO: Should sync subsurfaces avoid requesting frame callbacks?
|
||||||
QReadLocker lock(&mSurfaceLock);
|
QReadLocker lock(&mSurfaceLock);
|
||||||
if (!mSurface)
|
if (!mSurface)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QMutexLocker locker(&mFrameSyncMutex);
|
QMutexLocker locker(&mFrameSyncMutex);
|
||||||
|
if (mWaitingForFrameCallback)
|
||||||
|
return;
|
||||||
|
|
||||||
struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object()));
|
struct ::wl_surface *wrappedSurface = reinterpret_cast<struct ::wl_surface *>(wl_proxy_create_wrapper(mSurface->object()));
|
||||||
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mDisplay->frameEventQueue());
|
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(wrappedSurface), mDisplay->frameEventQueue());
|
||||||
|
@ -261,12 +261,13 @@ protected:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
WId mWindowId;
|
WId mWindowId;
|
||||||
bool mWaitingForFrameCallback = false;
|
|
||||||
bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out
|
bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out
|
||||||
QAtomicInt mWaitingForUpdateDelivery = false;
|
|
||||||
int mFrameCallbackCheckIntervalTimerId = -1;
|
int mFrameCallbackCheckIntervalTimerId = -1;
|
||||||
QElapsedTimer mFrameCallbackElapsedTimer;
|
QAtomicInt mWaitingForUpdateDelivery = false;
|
||||||
struct ::wl_callback *mFrameCallback = nullptr;
|
|
||||||
|
bool mWaitingForFrameCallback = false; // Protected by mFrameSyncMutex
|
||||||
|
QElapsedTimer mFrameCallbackElapsedTimer; // Protected by mFrameSyncMutex
|
||||||
|
struct ::wl_callback *mFrameCallback = nullptr; // Protected by mFrameSyncMutex
|
||||||
QMutex mFrameSyncMutex;
|
QMutex mFrameSyncMutex;
|
||||||
QWaitCondition mFrameSyncWait;
|
QWaitCondition mFrameSyncWait;
|
||||||
|
|
||||||
@ -323,7 +324,7 @@ private:
|
|||||||
QRect mLastExposeGeometry;
|
QRect mLastExposeGeometry;
|
||||||
|
|
||||||
static const wl_callback_listener callbackListener;
|
static const wl_callback_listener callbackListener;
|
||||||
void handleFrameCallback();
|
void handleFrameCallback(struct ::wl_callback* callback);
|
||||||
|
|
||||||
static QWaylandWindow *mMouseGrab;
|
static QWaylandWindow *mMouseGrab;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user