diff options
author | David Edmundson <davidedmundson@kde.org> | 2022-01-28 16:24:32 +0000 |
---|---|---|
committer | David Edmundson <davidedmundson@kde.org> | 2023-03-07 13:26:50 +0000 |
commit | 26d8603b523af973682b7e602f1158ae62f21c9b (patch) | |
tree | cf5223fea624e961af7150fdde44bdf1e9cfa27a /src/client/qwaylanddisplay.cpp | |
parent | 83599e16789f945ef15954f2a2aabb37b245e596 (diff) |
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/client/qwaylanddisplay.cpp')
-rw-r--r-- | src/client/qwaylanddisplay.cpp | 161 |
1 files changed, 143 insertions, 18 deletions
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 46c85a6a3..5c3431a05 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() |