From 3ca7c39e69e38123c774232826433ecba003f869 Mon Sep 17 00:00:00 2001 From: shenleban tongying Date: Wed, 21 Aug 2024 20:19:35 -0400 Subject: [PATCH] feat: properly handle Unix signals, like `SIGTERM`, for graceful exit (#1732) * feat: handle UNIX shutting down SIGNALS * Convey ksignalhandler from LGPLv2.1+ to GPLv3+ which allowed/required --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/main.cc | 7 +++ src/unix/ksignalhandler.cc | 99 ++++++++++++++++++++++++++++++++++++++ src/unix/ksignalhandler.hh | 68 ++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 src/unix/ksignalhandler.cc create mode 100644 src/unix/ksignalhandler.hh diff --git a/src/main.cc b/src/main.cc index b40656cf..7415bc83 100644 --- a/src/main.cc +++ b/src/main.cc @@ -14,6 +14,7 @@ #if defined( Q_OS_UNIX ) #include + #include "unix/ksignalhandler.hh" #endif #ifdef Q_OS_WIN32 @@ -590,6 +591,12 @@ int main( int argc, char ** argv ) if ( gdcl.needTranslateWord() ) m.wordReceived( gdcl.wordToTranslate() ); +#ifdef Q_OS_UNIX + // handle Unix's shutdown signals for graceful exit + KSignalHandler::self()->watchSignal( SIGINT ); + KSignalHandler::self()->watchSignal( SIGTERM ); + QObject::connect( KSignalHandler::self(), &KSignalHandler::signalReceived, &m, &MainWindow::quitApp ); +#endif int r = app.exec(); app.removeDataCommiter( m ); diff --git a/src/unix/ksignalhandler.cc b/src/unix/ksignalhandler.cc new file mode 100644 index 00000000..c6c48c88 --- /dev/null +++ b/src/unix/ksignalhandler.cc @@ -0,0 +1,99 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-3.0-or-later + + Copied from KDE's KCoreAddons with minor modifications +*/ +#include +#ifdef Q_OS_UNIX + + #include "ksignalhandler.hh" + #include + #include + #include + #include + #include + #include + #include + #include + +class KSignalHandlerPrivate: public QObject +{ +public: + static void signalHandler( int signal ); + void handleSignal(); + + QSet< int > m_signalsRegistered; + static int signalFd[ 2 ]; + QSocketNotifier * m_handler = nullptr; + + KSignalHandler * q; +}; + +int KSignalHandlerPrivate::signalFd[ 2 ]; + +KSignalHandler::KSignalHandler(): + d( new KSignalHandlerPrivate ) +{ + d->q = this; + if ( ::socketpair( AF_UNIX, SOCK_STREAM, 0, KSignalHandlerPrivate::signalFd ) ) { + qDebug() << "Couldn't create a socketpair"; + return; + } + + // ensure the sockets are not leaked to child processes, SOCK_CLOEXEC not supported on macOS + fcntl( KSignalHandlerPrivate::signalFd[ 0 ], F_SETFD, FD_CLOEXEC ); + fcntl( KSignalHandlerPrivate::signalFd[ 1 ], F_SETFD, FD_CLOEXEC ); + + QTimer::singleShot( 0, [ this ] { + d->m_handler = new QSocketNotifier( KSignalHandlerPrivate::signalFd[ 1 ], QSocketNotifier::Read, this ); + connect( d->m_handler, &QSocketNotifier::activated, d.get(), &KSignalHandlerPrivate::handleSignal ); + } ); +} + +KSignalHandler::~KSignalHandler() +{ + for ( int sig : std::as_const( d->m_signalsRegistered ) ) { + signal( sig, nullptr ); + } + close( KSignalHandlerPrivate::signalFd[ 0 ] ); + close( KSignalHandlerPrivate::signalFd[ 1 ] ); +} + +void KSignalHandler::watchSignal( int signalToTrack ) +{ + d->m_signalsRegistered.insert( signalToTrack ); + signal( signalToTrack, KSignalHandlerPrivate::signalHandler ); +} + +void KSignalHandlerPrivate::signalHandler( int signal ) +{ + const int ret = ::write( signalFd[ 0 ], &signal, sizeof( signal ) ); + if ( ret != sizeof( signal ) ) { + qDebug() << "signalHandler couldn't write for signal" << strsignal( signal ) << " Got error:" << strerror( errno ); + } +} + +void KSignalHandlerPrivate::handleSignal() +{ + m_handler->setEnabled( false ); + int signal; + const int ret = ::read( KSignalHandlerPrivate::signalFd[ 1 ], &signal, sizeof( signal ) ); + if ( ret != sizeof( signal ) ) { + qDebug() << "handleSignal couldn't read signal for fd" << KSignalHandlerPrivate::signalFd[ 1 ] + << " Got error:" << strerror( errno ); + return; + } + m_handler->setEnabled( true ); + + Q_EMIT q->signalReceived( signal ); +} + +KSignalHandler * KSignalHandler::self() +{ + static KSignalHandler s_self; + return &s_self; +} + +#endif \ No newline at end of file diff --git a/src/unix/ksignalhandler.hh b/src/unix/ksignalhandler.hh new file mode 100644 index 00000000..c1de838b --- /dev/null +++ b/src/unix/ksignalhandler.hh @@ -0,0 +1,68 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-3.0-or-later + + Copied from KDE's KCoreAddons with minor modifications +*/ +#pragma once +#include +#ifdef Q_OS_UNIX + #include + #include + +class KSignalHandlerPrivate; + +/** + * Allows getting ANSI C signals and forward them onto the Qt eventloop. + * + * It's a singleton as it relies on static data getting defined. + * + * \code + * { + * KSignalHandler::self()->watchSignal(SIGTERM); + * connect(KSignalHandler::self(), &KSignalHandler::signalReceived, + * this, &SomeClass::handleSignal); + * job->start(); + * } + * \endcode + * + * @since 5.92 + */ +class KSignalHandler: public QObject +{ + Q_OBJECT + +public: + ~KSignalHandler() override; + + /** + * Adds @p signal to be watched for. Once the process is notified about this signal, @m signalReceived will be emitted with the same @p signal as an + * argument. + * + * @see signalReceived + */ + void watchSignal( int signal ); + + /** + * Fetches an instance we can use to register our signals. + */ + static KSignalHandler * self(); + +Q_SIGNALS: + /** + * Notifies that @p signal is emitted. + * + * To catch a signal, we need to make sure it's registered using @m watchSignal. + * + * @see watchSignal + */ + void signalReceived( int signal ); + +private: + KSignalHandler(); + + QScopedPointer< KSignalHandlerPrivate > d; +}; + +#endif \ No newline at end of file