Skip to content

Commit

Permalink
feat: properly handle Unix signals, like SIGTERM, for graceful exit (
Browse files Browse the repository at this point in the history
…#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>
  • Loading branch information
shenlebantongying and autofix-ci[bot] authored Aug 22, 2024
1 parent da1c67c commit 3ca7c39
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#if defined( Q_OS_UNIX )
#include <clocale>
#include "unix/ksignalhandler.hh"
#endif

#ifdef Q_OS_WIN32
Expand Down Expand Up @@ -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 );
Expand Down
99 changes: 99 additions & 0 deletions src/unix/ksignalhandler.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <[email protected]>
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
68 changes: 68 additions & 0 deletions src/unix/ksignalhandler.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <[email protected]>
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

0 comments on commit 3ca7c39

Please sign in to comment.