From 71ade59f4b7deed2eaf066d0594ee732380b4b0d Mon Sep 17 00:00:00 2001 From: Aleix Pol Gonzalez Date: Tue, 2 Jan 2024 18:34:26 +0100 Subject: [PATCH] systemd: Set up a watchdog Allows to notify systemd whether kwin is still running and possibly restart the service if it stops responding. Use Type=notify-reload to watch the kwin service. This will make it so we receive SIGHUP rather than SIGTERM on the wrapper which we can handle gracefully and stop the kwin process and restart as expected. https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html Signed-off-by: Victoria Fischer --- CMakeLists.txt | 3 + plasma-kwin_wayland.service.in | 4 ++ src/CMakeLists.txt | 5 ++ src/helpers/wayland_wrapper/kwin_wrapper.cpp | 41 +++++++++---- src/watchdog.cpp | 62 ++++++++++++++++++++ 5 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/watchdog.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a783d01a0..b910028aec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,6 +355,9 @@ set_package_properties(QAccessibilityClient6 PROPERTIES ) set(HAVE_ACCESSIBILITY ${QAccessibilityClient6_FOUND}) +pkg_check_modules(libsystemd IMPORTED_TARGET libsystemd) +add_feature_info(libsystemd libsystemd_FOUND "Required for setting up the service watchdog") + option(KWIN_BUILD_GLOBALSHORTCUTS "Enable building of KWin with global shortcuts support" ON) if(KWIN_BUILD_GLOBALSHORTCUTS) find_package(KGlobalAccelD REQUIRED) diff --git a/plasma-kwin_wayland.service.in b/plasma-kwin_wayland.service.in index 6db09b26ba..d4a4cf6216 100644 --- a/plasma-kwin_wayland.service.in +++ b/plasma-kwin_wayland.service.in @@ -3,6 +3,10 @@ Description=KDE Window Manager PartOf=graphical-session.target [Service] +Type=notify-reload ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/kwin_wayland_wrapper --xwayland BusName=org.kde.KWinWrapper Slice=session.slice +WatchdogSec=5s +NotifyAccess=all +WatchdogSignal=SIGHUP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e0db4fe9a..e5f22b3143 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -355,6 +355,11 @@ if (KWIN_BUILD_TABBOX) add_subdirectory(tabbox/switchers) endif() +if(TARGET PkgConfig::libsystemd) + target_sources(kwin PRIVATE watchdog.cpp) + target_link_libraries(kwin PRIVATE PkgConfig::libsystemd) +endif() + qt_generate_dbus_interface(virtualkeyboard_dbus.h org.kde.kwin.VirtualKeyboard.xml OPTIONS -A) qt_generate_dbus_interface(tabletmodemanager.h org.kde.KWin.TabletModeManager.xml OPTIONS -A) diff --git a/src/helpers/wayland_wrapper/kwin_wrapper.cpp b/src/helpers/wayland_wrapper/kwin_wrapper.cpp index 9f08261faf..e2634b2ba9 100644 --- a/src/helpers/wayland_wrapper/kwin_wrapper.cpp +++ b/src/helpers/wayland_wrapper/kwin_wrapper.cpp @@ -42,7 +42,10 @@ class KWinWrapper : public QObject public: KWinWrapper(QObject *parent); ~KWinWrapper(); + void run(); + void restart(); + void terminate(); private: wl_socket *m_socket; @@ -82,13 +85,7 @@ KWinWrapper::KWinWrapper(QObject *parent) KWinWrapper::~KWinWrapper() { wl_socket_destroy(m_socket); - if (m_kwinProcess) { - disconnect(m_kwinProcess, nullptr, this, nullptr); - m_kwinProcess->terminate(); - m_kwinProcess->waitForFinished(); - m_kwinProcess->kill(); - m_kwinProcess->waitForFinished(); - } + terminate(); } void KWinWrapper::run() @@ -147,7 +144,6 @@ void KWinWrapper::run() env.insert("XAUTHORITY", m_xauthorityFile.fileName()); } } - auto envSyncJob = new KUpdateLaunchEnvironmentJob(env); connect(envSyncJob, &KUpdateLaunchEnvironmentJob::finished, this, []() { // The service name is merely there to indicate to the world that we're up and ready with all envs exported @@ -155,21 +151,42 @@ void KWinWrapper::run() }); } +void KWinWrapper::terminate() +{ + if (m_kwinProcess) { + disconnect(m_kwinProcess, nullptr, this, nullptr); + m_kwinProcess->terminate(); + m_kwinProcess->waitForFinished(); + m_kwinProcess->kill(); + m_kwinProcess->waitForFinished(); + } +} + +void KWinWrapper::restart() +{ + terminate(); + m_kwinProcess->start(); +} + int main(int argc, char **argv) { QCoreApplication app(argc, argv); app.setQuitLockEnabled(false); // don't exit when the first KJob finishes KSignalHandler::self()->watchSignal(SIGTERM); - QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app](int signal) { + KSignalHandler::self()->watchSignal(SIGHUP); + + KWinWrapper wrapper(&app); + wrapper.run(); + + QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app, &wrapper](int signal) { if (signal == SIGTERM) { app.quit(); + } else if (signal == SIGHUP) { // The systemd service will issue SIGHUP when it's locked up so that we can restarted + wrapper.restart(); } }); - KWinWrapper wrapper(&app); - wrapper.run(); - return app.exec(); } diff --git a/src/watchdog.cpp b/src/watchdog.cpp new file mode 100644 index 0000000000..e9a6b92947 --- /dev/null +++ b/src/watchdog.cpp @@ -0,0 +1,62 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Aleix Pol i Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "utils/common.h" +#include +#include +#include + +class Watchdog : public QObject +{ + Q_OBJECT +public: + Watchdog(QObject *parent) + : QObject(parent) + { + bool ok; + const std::chrono::microseconds watchdogIntervalInUs((qgetenv("WATCHDOG_USEC").toUInt(&ok) * 3) / 4); + if (!ok) { + qCInfo(KWIN_CORE) << "Watchdog: disabled, not running on a systemd environment or watchdog is not set up. No WATCHDOG_USEC."; + deleteLater(); + return; + } + m_onBehalf = qgetenv("WATCHDOG_PID").toUInt(&ok); + if (!ok) { + qCInfo(KWIN_CORE) << "Watchdog: disabled, not running on a systemd environment or watchdog is not set up. No WATCHDOG_PID."; + deleteLater(); + return; + } + qunsetenv("WATCHDOG_USEC"); + qunsetenv("WATCHDOG_PID"); + auto t = new QTimer(this); + t->setInterval(std::chrono::duration_cast(watchdogIntervalInUs)); + t->setSingleShot(false); + qCInfo(KWIN_CORE) << "Watchdog: enabled. Interval:" << watchdogIntervalInUs << t->intervalAsDuration(); + + sd_pid_notify(m_onBehalf, 0, "READY=1"); // If service Type=notify the service is only considered ready once we send this + qCInfo(KWIN_CORE) << "Watchdog: Notified as ready"; + + connect(t, &QTimer::timeout, this, [this] { + sd_pid_notify(m_onBehalf, 0, "WATCHDOG=1"); + }); + t->start(); + } + +private: + pid_t m_onBehalf = 0; +}; + +static void setupWatchdog() +{ + new Watchdog(QCoreApplication::instance()); +} + +Q_COREAPP_STARTUP_FUNCTION(setupWatchdog) + +#include "watchdog.moc"