Index: kdevplatform/shell/CMakeLists.txt =================================================================== --- kdevplatform/shell/CMakeLists.txt +++ kdevplatform/shell/CMakeLists.txt @@ -159,6 +159,10 @@ if(APPLE) target_link_libraries(KDevPlatformShell PRIVATE "-framework AppKit") endif() +if(UNIX) + target_link_libraries(KDevPlatformShell PRIVATE "-pthreads") +endif() + install(FILES mainwindow.h Index: kdevplatform/shell/core.cpp =================================================================== --- kdevplatform/shell/core.cpp +++ kdevplatform/shell/core.cpp @@ -21,6 +21,9 @@ #include "core_p.h" #include +#ifndef USE_QSOCKETNOTIFIER +#include +#endif #include @@ -50,40 +53,13 @@ #include -namespace { -void shutdownGracefully(int sig) -{ - static volatile std::sig_atomic_t handlingSignal = 0; - - if ( !handlingSignal ) { - handlingSignal = 1; - qCDebug(SHELL) << "signal " << sig << " received, shutting down gracefully"; - QCoreApplication* app = QCoreApplication::instance(); - if (QApplication* guiApp = qobject_cast(app)) { - guiApp->closeAllWindows(); - } - app->quit(); - return; - } - - // re-raise signal with default handler and trigger program termination - std::signal(sig, SIG_DFL); - std::raise(sig); -} - -void installSignalHandler() -{ -#ifdef SIGHUP - std::signal(SIGHUP, shutdownGracefully); -#endif -#ifdef SIGINT - std::signal(SIGINT, shutdownGracefully); -#endif -#ifdef SIGTERM - std::signal(SIGTERM, shutdownGracefully); -#endif -} -} +#ifdef Q_OS_MACOS +// Darwin doesn't have unnamed POSIX semaphores but can use MACH semaphores. +#define sem_init(s,x,value) semaphore_create(mach_task_self(), (s), SYNC_POLICY_FIFO, (value)) +#define sem_wait(s) semaphore_wait(*(s)) +#define sem_post(s) semaphore_signal(*(s)) +#define sem_destroy(s) semaphore_destroy(mach_task_self(), *(s)) +#endif // Q_OS_MACOS namespace KDevelop { @@ -120,9 +96,14 @@ return aboutData; } +Core* CorePrivate::s_core = nullptr; +CorePrivate* CorePrivate::s_self = nullptr; + CorePrivate::CorePrivate(Core *core): - m_aboutData( createAboutData() ), m_core(core), m_cleanedUp(false), m_shuttingDown(false) + QObject(nullptr), + m_aboutData( createAboutData() ), m_cleanedUp(false), m_shuttingDown(false) { + s_core = core; } bool CorePrivate::initialize(Core::Setup mode, const QString& session ) @@ -133,22 +114,22 @@ if( !sessionController ) { - sessionController = new SessionController(m_core); + sessionController = new SessionController(s_core); } if( !workingSetController && !(mode & Core::NoUi) ) { workingSetController = new WorkingSetController(); } qCDebug(SHELL) << "Creating ui controller"; if( !uiController ) { - uiController = new UiController(m_core); + uiController = new UiController(s_core); } qCDebug(SHELL) << "Creating plugin controller"; if( !pluginController ) { - pluginController = new PluginController(m_core); + pluginController = new PluginController(s_core); const auto pluginInfos = pluginController->allPluginInfos(); if (pluginInfos.isEmpty()) { QMessageBox::critical(nullptr, @@ -163,34 +144,34 @@ } if( !partController && !(mode & Core::NoUi)) { - partController = new PartController(m_core, uiController->defaultMainWindow()); + partController = new PartController(s_core, uiController->defaultMainWindow()); } if( !projectController ) { - projectController = new ProjectController(m_core); + projectController = new ProjectController(s_core); } if( !documentController ) { - documentController = new DocumentController(m_core); + documentController = new DocumentController(s_core); } if( !languageController ) { // Must be initialized after documentController, because the background parser depends // on the document controller. - languageController = new LanguageController(m_core); + languageController = new LanguageController(s_core); } if( !runController ) { - runController = new RunController(m_core); + runController = new RunController(s_core); } if( !sourceFormatterController ) { - sourceFormatterController = new SourceFormatterController(m_core); + sourceFormatterController = new SourceFormatterController(s_core); } if ( !progressController) @@ -200,27 +181,27 @@ if( !selectionController ) { - selectionController = new SelectionController(m_core); + selectionController = new SelectionController(s_core); } if( !documentationController && !(mode & Core::NoUi) ) { - documentationController = new DocumentationController(m_core); + documentationController = new DocumentationController(s_core); } if( !runtimeController ) { - runtimeController = new RuntimeController(m_core); + runtimeController = new RuntimeController(s_core); } if( !debugController ) { - debugController = new DebugController(m_core); + debugController = new DebugController(s_core); } if( !testController ) { - testController = new TestController(m_core); + testController = new TestController(s_core); } qCDebug(SHELL) << "Done creating controllers"; @@ -245,7 +226,7 @@ // has been initialized. At that point we know whether there are projects loading // which the background parser is handling internally to defer parse jobs QObject::connect(projectController.data(), &ProjectController::initialized, - m_core, [this]() { + s_core, [this]() { languageController->backgroundParser()->resume(); }); @@ -289,7 +270,59 @@ testController->initialize(); runtimeController->initialize(); - installSignalHandler(); +// A "proper" exit-on-signal approach: +// Open a pipe or an eventfd, then install your signal handler. In that signal +// handler, write anything to the writing end or write uint64_t(1) the eventfd. +// Create a QSocketNotifier on the reading end of the pipe or on the eventfd, +// connect its activation signal to a slot that does what you want. + +#ifdef Q_OS_UNIX + if (!s_self) { +#ifdef USE_QSOCKETNOTIFIER + int pp[2]; + if (pipe(pp)) { + qCWarning(SHELL) << "Error opening signal handler pipe" << strerror(errno); + } else { + m_signalPipeRead = pp[0]; + m_signalPipeWrite = pp[1]; + m_signalNotifier = new QSocketNotifier(m_signalPipeRead, QSocketNotifier::Read, this); + connect(m_signalNotifier, &QSocketNotifier::activated, this, &CorePrivate::shutdownGracefully); + std::signal(SIGHUP, signalHandler); + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + m_signalNotifier->setEnabled(true); + } +#else + if (!m_monitorSignals) { + if (sem_init(&m_sem, 0, 0) != -1) { + m_monitorSignals = true; + m_monitorHandle = QtConcurrent::run([=] (){ + int s; + QThread::currentThread()->setObjectName(QStringLiteral("signal() monitor")); + while (m_monitorSignals + && (((s = sem_wait(&m_sem)) == -1 && errno == EINTR) || s == 0)) { + if (m_monitorSignals) { + if (s == 0) { + // the semaphore was triggered + emit interruptSignalReceived(m_signalReceived); + } + } + continue; /* Restart if interrupted by handler */ + } + sem_destroy(&m_sem); + }); + connect(this, &CorePrivate::interruptSignalReceived, this, &CorePrivate::shutdownGracefully); + std::signal(SIGHUP, signalHandler); + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + } else { + qCCritical(SHELL) << "Couldn't create semaphore:" << strerror(errno); + } + } + s_self = this; + } +#endif +#endif // Q_OS_UNIX qCDebug(SHELL) << "Done initializing controllers"; @@ -328,8 +361,91 @@ workingSetController.clear(); testController.clear(); runtimeController.clear(); + +#ifdef USE_QSOCKETNOTIFIER + if (m_signalPipeWrite != -1) { + close(m_signalPipeWrite); + } + if (m_signalPipeRead != -1) { + close(m_signalPipeRead); + } +#else + if (m_monitorSignals) { + m_monitorSignals = false; + sem_post(&m_sem); + } +#endif } +void CorePrivate::signalHandler(int sig) +{ + s_self->m_signalReceived = sig; +#ifdef USE_QSOCKETNOTIFIER + if (s_self->m_signalPipeWrite != -1) { + qCDebug(SHELL) << "signal" << sig << " received, shutting down gracefully"; + write(s_self->m_signalPipeWrite, &sig, sizeof(sig)); + } +#else + if (s_self->m_monitorSignals) { + qCWarning(SHELL) << "signal" << sig << " received, shutting down gracefully"; + sem_post(&s_self->m_sem); + } +#endif +} + +void CorePrivate::shutdownGracefully() +{ + static volatile std::sig_atomic_t handlingSignal = 0; + + // exit as quickly as possible when a SIGHUP is received. + // (SIGHUP is not available on MS Windows.) + if ( !handlingSignal +#ifdef SIGHUP + && m_signalReceived != SIGHUP +#endif + ) { + handlingSignal = 1; + qCWarning(SHELL) << "Going down on signal" << m_signalReceived; + QCoreApplication* app = QCoreApplication::instance(); + if (QApplication* guiApp = qobject_cast(app)) { + guiApp->closeAllWindows(); + } + app->quit(); + return; + } + + int sig = m_signalReceived; + m_signalReceived = 0; + + // restore the default signal handler; + std::signal(sig, SIG_DFL); + + if (s_core) { + // shutdown core functionality, in particular the DUChain subsystem + // in an effort to prevent cache corruption. It's only cache, but + // regenerating it can be very time-consuming. + s_core->shutdown(); + } + +#ifdef USE_QSOCKETNOTIFIER + m_signalNotifier->setEnabled(false); +#else + // the default signal handlers bypass all dtors so we terminate the + // monitor in order to release our semaphore. + if (m_monitorSignals) { + m_monitorSignals = false; + sem_post(&m_sem); + } + if (m_monitorHandle.isRunning()) { + m_monitorHandle.waitForFinished(); + } +#endif + + // re-raise signal with default handler and trigger program termination + std::raise(sig); +} + + bool Core::initialize(Setup mode, const QString& session) { if (m_self) @@ -604,4 +720,3 @@ } } - Index: kdevplatform/shell/core_p.h =================================================================== --- kdevplatform/shell/core_p.h +++ kdevplatform/shell/core_p.h @@ -26,6 +26,18 @@ #include #include +#ifdef USE_QSOCKETNOTIFIER +#include +#else +#ifdef Q_OS_MACOS + #include + typedef semaphore_t sem_t; +#else + #include +#endif // Q_OS_MACOS +#include +#include +#endif namespace KDevelop { @@ -47,11 +59,21 @@ class TestController; class RuntimeController; -class KDEVPLATFORMSHELL_EXPORT CorePrivate { +class KDEVPLATFORMSHELL_EXPORT CorePrivate : public QObject { + Q_OBJECT + +#ifndef USE_QSOCKETNOTIFIER +Q_SIGNALS: + void interruptSignalReceived(int sig); +#endif + public: explicit CorePrivate(Core *core); ~CorePrivate(); bool initialize( Core::Setup mode, const QString& session ); + void shutdownGracefully(); + static void signalHandler(int sig); + QPointer pluginController; QPointer uiController; QPointer projectController; @@ -70,10 +92,21 @@ QPointer runtimeController; const KAboutData m_aboutData; - Core* const m_core; + static Core* s_core; bool m_cleanedUp; bool m_shuttingDown; Core::Setup m_mode; +#ifdef USE_QSOCKETNOTIFIER + int m_signalPipeRead = -1; + int m_signalPipeWrite = -1; + QSocketNotifier* m_signalNotifier = nullptr; +#else + sem_t m_sem; + sig_atomic_t m_monitorSignals = false; + QFuture m_monitorHandle; +#endif + sig_atomic_t m_signalReceived = 0; + static CorePrivate* s_self; }; }