summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Edmundson <davidedmundson@kde.org>2022-01-28 16:24:32 +0000
committerDavid Edmundson <davidedmundson@kde.org>2023-03-07 13:26:50 +0000
commit26d8603b523af973682b7e602f1158ae62f21c9b (patch)
treecf5223fea624e961af7150fdde44bdf1e9cfa27a /src
parent83599e16789f945ef15954f2a2aabb37b245e596 (diff)
downloadqtwayland-26d8603b523af973682b7e602f1158ae62f21c9b.tar.gz
Introduce path for surviving compositor restarts
This patch introduces an optional mechanism for clients to survive a crash and reconnect seemingly seamlessly. In the event of a disconnect from the compositor socket we simply try to reconnect again and replay any data needed so that we maintain a consistent state to where we left off. From an application point-of-view any open popups will be dismissed and we we potentially get a new framecallback, but it will be almost entirely transparent. Users of custom QWaylandClientExtensions will be notified via the activeChanged signal and rebuild as though the compositor had withdrawn and re-announced the global. OpenGL contexts will be marked as invalid, and handled the same way as a GPU reset. On the next frame RHI will notice these are invalid and recreate them, only now against a new wl_display and new EGLDisplay. Users of low level EGL/native objects might be affected, but the alternative at this point is being closed anyway. The entire codepath is only activated via an environment variable. Change-Id: I6c4acc885540e14cead7640794df86dd974fef4f Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/client/qwaylanddisplay.cpp161
-rw-r--r--src/client/qwaylanddisplay_p.h5
-rw-r--r--src/client/qwaylandintegration.cpp11
-rw-r--r--src/client/qwaylandintegration_p.h1
-rw-r--r--src/client/qwaylandshmbackingstore.cpp13
-rw-r--r--src/client/qwaylandwindow.cpp20
-rw-r--r--src/client/qwaylandwindow_p.h5
-rw-r--r--src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp6
-rw-r--r--src/hardwareintegration/client/wayland-egl/qwaylandeglwindow_p.h1
-rw-r--r--src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp9
-rw-r--r--src/hardwareintegration/client/wayland-egl/qwaylandglcontext_p.h1
11 files changed, 212 insertions, 21 deletions
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
index 46c85a6a..5c3431a0 100644
--- a/src/client/qwaylanddisplay.cpp
+++ b/src/client/qwaylanddisplay.cpp
@@ -65,18 +65,6 @@
#include <tuple> // 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 {
@@ -114,9 +102,13 @@ public:
* not only the one issued from event thread's waitForReading(), which means functions
* called from dispatch_pending() can safely spin an event loop.
*/
+ if (m_quitting)
+ return;
+
for (;;) {
if (dispatchQueuePending() < 0) {
- checkWaylandError(m_wldisplay);
+ Q_EMIT waylandError();
+ m_quitting = true;
return;
}
@@ -154,6 +146,7 @@ public:
Q_SIGNALS:
void needReadAndDispatch();
+ void waylandError();
protected:
void run() override
@@ -328,11 +321,17 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration)
qRegisterMetaType<uint32_t>("uint32_t");
mDisplay = wl_display_connect(nullptr);
- if (!mDisplay) {
+ if (mDisplay) {
+ setupConnection();
+ } else {
qErrnoWarning(errno, "Failed to create wl_display");
- return;
}
+ mWaylandTryReconnect = qEnvironmentVariableIsSet("QT_WAYLAND_RECONNECT");
+}
+
+void QWaylandDisplay::setupConnection()
+{
struct ::wl_registry *registry = wl_display_get_registry(mDisplay);
init(registry);
@@ -404,7 +403,110 @@ void QWaylandDisplay::ensureScreen()
Q_ASSERT(!QGuiApplication::screens().empty());
}
-// Called in main thread, either from queued signal or directly.
+void QWaylandDisplay::reconnect()
+{
+ qCWarning(lcQpaWayland) << "Attempting wayland reconnect";
+ m_eventThread->stop();
+ m_frameEventQueueThread->stop();
+ m_eventThread->wait();
+ m_frameEventQueueThread->wait();
+
+ qDeleteAll(mWaitingScreens);
+ mWaitingScreens.clear();
+
+ // mCompositor
+ mShm.reset();
+ mCursorThemes.clear();
+ mCursor.reset();
+ mDndSelectionHandler.reset();
+ mWindowExtension.reset();
+ mSubCompositor.reset();
+ mTouchExtension.reset();
+ mQtKeyExtension.reset();
+ mWindowManagerIntegration.reset();
+ mTabletManager.reset();
+ mPointerGestures.reset();
+#if QT_CONFIG(wayland_client_primary_selection)
+ mPrimarySelectionManager.reset();
+#endif
+ mTextInputMethodManager.reset();
+ mTextInputManagerv1.reset();
+ mTextInputManagerv2.reset();
+ mTextInputManagerv4.reset();
+ mHardwareIntegration.reset();
+ mXdgOutputManager.reset();
+ mViewporter.reset();
+ mFractionalScaleManager.reset();
+
+ mWaylandIntegration->reset();
+
+ qDeleteAll(std::exchange(mInputDevices, {}));
+ mLastInputDevice = nullptr;
+
+ auto screens = mScreens;
+ mScreens.clear();
+
+ for (const RegistryGlobal &global : mGlobals) {
+ emit globalRemoved(global);
+ }
+ mGlobals.clear();
+
+ mLastInputSerial = 0;
+ mLastInputWindow.clear();
+ mLastKeyboardFocus.clear();
+ mActiveWindows.clear();
+
+ const auto windows = QGuiApplication::allWindows();
+ for (auto window : windows) {
+ if (auto waylandWindow = dynamic_cast<QWaylandWindow *>(window->handle()))
+ waylandWindow->closeChildPopups();
+ }
+ // Remove windows that do not need to be recreated and now closed popups
+ QList<QWaylandWindow *> recreateWindows;
+ for (auto window : std::as_const(windows)) {
+ auto waylandWindow = dynamic_cast<QWaylandWindow*>((window)->handle());
+ if (waylandWindow && waylandWindow->wlSurface()) {
+ waylandWindow->reset();
+ recreateWindows.push_back(waylandWindow);
+ }
+ }
+
+ if (mSyncCallback) {
+ wl_callback_destroy(mSyncCallback);
+ mSyncCallback = nullptr;
+ }
+
+ mDisplay = wl_display_connect(nullptr);
+ if (!mDisplay)
+ _exit(1);
+
+ setupConnection();
+ initialize();
+
+ if (m_frameEventQueue)
+ wl_event_queue_destroy(m_frameEventQueue);
+ initEventThread();
+
+ emit reconnected();
+
+ auto needsRecreate = [](QPlatformWindow *window) {
+ return window && !static_cast<QWaylandWindow *>(window)->wlSurface();
+ };
+ auto window = recreateWindows.begin();
+ while (!recreateWindows.isEmpty()) {
+ if (!needsRecreate((*window)->QPlatformWindow::parent()) && !needsRecreate((*window)->transientParent())) {
+ (*window)->reinit();
+ window = recreateWindows.erase(window);
+ } else {
+ ++window;
+ }
+ if (window == recreateWindows.end())
+ window = recreateWindows.begin();
+ }
+
+ mWaylandIntegration->reconfigureInputContext();
+}
+
void QWaylandDisplay::flushRequests()
{
m_eventThread->readAndDispatchEvents();
@@ -419,6 +521,8 @@ void QWaylandDisplay::initEventThread()
new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch));
connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this,
&QWaylandDisplay::flushRequests, Qt::QueuedConnection);
+ connect(m_eventThread.get(), &EventThread::waylandError, this,
+ &QWaylandDisplay::checkWaylandError, Qt::QueuedConnection);
m_eventThread->start();
// wl_display_disconnect() free this.
@@ -428,10 +532,31 @@ void QWaylandDisplay::initEventThread()
m_frameEventQueueThread->start();
}
+void QWaylandDisplay::checkWaylandError()
+{
+ int ecode = wl_display_get_error(mDisplay);
+ if ((ecode == EPIPE || ecode == ECONNRESET)) {
+ qWarning("The Wayland connection broke. Did the Wayland compositor die?");
+ if (mWaylandTryReconnect) {
+ reconnect();
+ return;
+ }
+ } else {
+ qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode));
+ }
+ _exit(-1);
+}
+
void QWaylandDisplay::blockingReadEvents()
{
- if (wl_display_dispatch(mDisplay) < 0)
- checkWaylandError(mDisplay);
+ if (wl_display_dispatch(mDisplay) < 0) {
+ int ecode = wl_display_get_error(mDisplay);
+ if ((ecode == EPIPE || ecode == ECONNRESET))
+ qWarning("The Wayland connection broke during blocking read event. Did the Wayland compositor die?");
+ else
+ qWarning("The Wayland connection experienced a fatal error during blocking read event: %s", strerror(ecode));
+ _exit(-1);
+ }
}
void QWaylandDisplay::checkTextInputProtocol()
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
index 640f33b8..f5c1bcbb 100644
--- a/src/client/qwaylanddisplay_p.h
+++ b/src/client/qwaylanddisplay_p.h
@@ -200,10 +200,14 @@ public slots:
void flushRequests();
signals:
+ void reconnected();
void globalAdded(const RegistryGlobal &global);
void globalRemoved(const RegistryGlobal &global);
private:
+ void checkWaylandError();
+ void reconnect();
+ void setupConnection();
void handleWaylandSync();
void requestWaylandSync();
@@ -283,6 +287,7 @@ private:
QList<QWaylandWindow *> mActiveWindows;
struct wl_callback *mSyncCallback = nullptr;
static const wl_callback_listener syncCallbackListener;
+ bool mWaylandTryReconnect = false;
bool mClientSideInputContextRequested = [] () {
const QString& requested = QPlatformInputContextFactory::requested();
diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp
index 13bacc9b..45a247f9 100644
--- a/src/client/qwaylandintegration.cpp
+++ b/src/client/qwaylandintegration.cpp
@@ -503,6 +503,17 @@ QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QStr
}
}
+void QWaylandIntegration::reset()
+{
+ mServerBufferIntegration.reset();
+ mServerBufferIntegrationInitialized = false;
+
+ mInputDeviceIntegration.reset();
+
+ mClientBufferIntegration.reset();
+ mClientBufferIntegrationInitialized = false;
+}
+
}
QT_END_NAMESPACE
diff --git a/src/client/qwaylandintegration_p.h b/src/client/qwaylandintegration_p.h
index 6221e689..1ba7a633 100644
--- a/src/client/qwaylandintegration_p.h
+++ b/src/client/qwaylandintegration_p.h
@@ -103,6 +103,7 @@ protected:
QScopedPointer<QWaylandDisplay> mDisplay;
protected:
+ void reset();
virtual QPlatformNativeInterface *createPlatformNativeInterface();
QScopedPointer<QWaylandClientBufferIntegration> mClientBufferIntegration;
diff --git a/src/client/qwaylandshmbackingstore.cpp b/src/client/qwaylandshmbackingstore.cpp
index b9bfc9c8..779ee45c 100644
--- a/src/client/qwaylandshmbackingstore.cpp
+++ b/src/client/qwaylandshmbackingstore.cpp
@@ -136,7 +136,18 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla
: QPlatformBackingStore(window)
, mDisplay(display)
{
-
+ QObject::connect(mDisplay, &QWaylandDisplay::reconnected, window, [this]() {
+ auto copy = mBuffers;
+ // clear available buffers so we create new ones
+ // actual deletion is deferred till after resize call so we can copy
+ // contents from the back buffer
+ mBuffers.clear();
+ mFrontBuffer = nullptr;
+ // resize always resets mBackBuffer
+ if (mRequestedSize.isValid() && waylandWindow())
+ resize(mRequestedSize);
+ qDeleteAll(copy);
+ });
}
QWaylandShmBackingStore::~QWaylandShmBackingStore()
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
index a77cce5a..90f4c609 100644
--- a/src/client/qwaylandwindow.cpp
+++ b/src/client/qwaylandwindow.cpp
@@ -291,11 +291,21 @@ void QWaylandWindow::reset()
mFrameCallbackElapsedTimer.invalidate();
mWaitingForFrameCallback = false;
}
+ if (mFrameCallbackCheckIntervalTimerId != -1) {
+ killTimer(mFrameCallbackCheckIntervalTimerId);
+ mFrameCallbackCheckIntervalTimerId = -1;
+ }
+
mFrameCallbackTimedOut = false;
mWaitingToApplyConfigure = false;
+ mCanResize = true;
+ mResizeDirty = false;
+ mOpaqueArea = QRegion();
mMask = QRegion();
+
mQueuedBuffer = nullptr;
+ mQueuedBufferDamage = QRegion();
mDisplay->handleWindowDestroyed(this);
}
@@ -1623,6 +1633,16 @@ void QWaylandWindow::closeChildPopups() {
popup->reset();
}
}
+
+void QWaylandWindow::reinit()
+{
+ if (window()->isVisible()) {
+ initWindow();
+ if (hasPendingUpdateRequest())
+ deliverUpdateRequest();
+ }
+}
+
}
QT_END_NAMESPACE
diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h
index 22b42a9a..d0640e7e 100644
--- a/src/client/qwaylandwindow_p.h
+++ b/src/client/qwaylandwindow_p.h
@@ -229,6 +229,9 @@ public:
void removeChildPopup(QWaylandWindow* child);
void closeChildPopups();
+ virtual void reinit();
+ void reset();
+
public slots:
void applyConfigure();
@@ -325,8 +328,6 @@ private:
void initializeWlSurface();
bool shouldCreateShellSurface() const;
bool shouldCreateSubSurface() const;
- void reset();
- static void closePopups(QWaylandWindow *parent);
QPlatformScreen *calculateScreenFromSurfaceEvents() const;
void setOpaqueArea(const QRegion &opaqueArea);
bool isOpaque() const;
diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp
index 8221d5d9..ca7d58f3 100644
--- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp
+++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp
@@ -140,6 +140,12 @@ void QWaylandEglWindow::invalidateSurface()
m_contentFBO = nullptr;
}
+void QWaylandEglWindow::reinit()
+{
+ QWaylandWindow::reinit();
+ m_clientBufferIntegration = static_cast<QWaylandEglClientBufferIntegration *>(mDisplay->clientBufferIntegration());
+}
+
EGLSurface QWaylandEglWindow::eglSurface() const
{
return m_eglSurface;
diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow_p.h b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow_p.h
index 5b9aa987..cfcdd577 100644
--- a/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow_p.h
+++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow_p.h
@@ -50,6 +50,7 @@ public:
void bindContentFBO();
void invalidateSurface() override;
+ void reinit() override;
private:
QWaylandEglClientBufferIntegration *m_clientBufferIntegration = nullptr;
diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp
index 0b64db5c..050b3eba 100644
--- a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp
+++ b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp
@@ -195,6 +195,10 @@ QWaylandGLContext::QWaylandGLContext(EGLDisplay eglDisplay, QWaylandDisplay *dis
const QSurfaceFormat &fmt, QPlatformOpenGLContext *share)
: QEGLPlatformContext(fmt, share, eglDisplay), m_display(display)
{
+ m_reconnectionWatcher = QObject::connect(m_display, &QWaylandDisplay::reconnected, [this]() {
+ invalidateContext();
+ });
+
switch (format().renderableType()) {
case QSurfaceFormat::OpenVG:
m_api = EGL_OPENVG_API;
@@ -260,6 +264,7 @@ void QWaylandGLContext::destroyTemporaryOffscreenSurface(EGLSurface eglSurface)
QWaylandGLContext::~QWaylandGLContext()
{
+ QObject::disconnect(m_reconnectionWatcher);
delete m_blitter;
m_blitter = nullptr;
if (m_decorationsContext != EGL_NO_CONTEXT)
@@ -280,6 +285,10 @@ void QWaylandGLContext::endFrame()
bool QWaylandGLContext::makeCurrent(QPlatformSurface *surface)
{
+ if (!isValid()) {
+ return false;
+ }
+
// in QWaylandGLContext() we called eglBindAPI with the correct value. However,
// eglBindAPI's documentation says:
// "eglBindAPI defines the current rendering API for EGL in the thread it is called from"
diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext_p.h b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext_p.h
index 00ef99b1..b985c667 100644
--- a/src/hardwareintegration/client/wayland-egl/qwaylandglcontext_p.h
+++ b/src/hardwareintegration/client/wayland-egl/qwaylandglcontext_p.h
@@ -62,6 +62,7 @@ private:
wl_surface *m_wlSurface = nullptr;
wl_egl_window *m_eglWindow = nullptr;
QWaylandEglWindow *m_currentWindow = nullptr;
+ QMetaObject::Connection m_reconnectionWatcher;
};
}