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>
This commit is contained in:
shenleban tongying 2024-08-21 20:19:35 -04:00 committed by GitHub
parent da1c67c35d
commit 3ca7c39e69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 174 additions and 0 deletions

View file

@ -14,6 +14,7 @@
#if defined( Q_OS_UNIX )
#include <clocale>
#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 );

View file

@ -0,0 +1,99 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-3.0-or-later
Copied from KDE's KCoreAddons with minor modifications
*/
#include <QtGlobal>
#ifdef Q_OS_UNIX
#include "ksignalhandler.hh"
#include <QSocketNotifier>
#include <QTimer>
#include <QDebug>
#include <cerrno>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <unistd.h>
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

View file

@ -0,0 +1,68 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-3.0-or-later
Copied from KDE's KCoreAddons with minor modifications
*/
#pragma once
#include <QtGlobal>
#ifdef Q_OS_UNIX
#include <QObject>
#include <signal.h>
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