File indexing completed on 2025-01-05 04:46:27
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> * 0003 * * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later * 0005 ***************************************************************************/ 0006 0007 #include "processcontrol.h" 0008 #include "akonadicontrol_debug.h" 0009 0010 #include "shared/akapplication.h" 0011 0012 #include "private/instance_p.h" 0013 #include "private/standarddirs_p.h" 0014 0015 #include <QTimer> 0016 0017 #ifdef Q_OS_UNIX 0018 #include <signal.h> 0019 #include <sys/types.h> 0020 #endif 0021 0022 using namespace Akonadi; 0023 using namespace std::chrono_literals; 0024 0025 static const int s_maxCrashCount = 2; 0026 0027 ProcessControl::ProcessControl(QObject *parent) 0028 : QObject(parent) 0029 , mShutdownTimeout(1s) 0030 { 0031 connect(&mProcess, &QProcess::errorOccurred, this, &ProcessControl::slotError); 0032 connect(&mProcess, &QProcess::finished, this, &ProcessControl::slotFinished); 0033 mProcess.setProcessChannelMode(QProcess::ForwardedChannels); 0034 0035 if (Akonadi::Instance::hasIdentifier()) { 0036 QProcessEnvironment env = mProcess.processEnvironment(); 0037 if (env.isEmpty()) { 0038 env = QProcessEnvironment::systemEnvironment(); 0039 } 0040 env.insert(QStringLiteral("AKONADI_INSTANCE"), Akonadi::Instance::identifier()); 0041 mProcess.setProcessEnvironment(env); 0042 } 0043 } 0044 0045 ProcessControl::~ProcessControl() 0046 { 0047 stop(); 0048 } 0049 0050 void ProcessControl::start(const QString &application, const QStringList &arguments, CrashPolicy policy) 0051 { 0052 mFailedToStart = false; 0053 0054 mApplication = application; 0055 mArguments = arguments; 0056 mPolicy = policy; 0057 0058 start(); 0059 } 0060 0061 void ProcessControl::setCrashPolicy(CrashPolicy policy) 0062 { 0063 mPolicy = policy; 0064 } 0065 0066 void ProcessControl::stop() 0067 { 0068 if (mProcess.state() != QProcess::NotRunning) { 0069 mProcess.waitForFinished(mShutdownTimeout.count()); 0070 mProcess.terminate(); 0071 mProcess.waitForFinished(std::chrono::milliseconds{10000}.count()); 0072 mProcess.kill(); 0073 } 0074 } 0075 0076 void ProcessControl::slotError(QProcess::ProcessError error) 0077 { 0078 switch (error) { 0079 case QProcess::Crashed: 0080 mCrashCount++; 0081 // do nothing, we'll respawn in slotFinished 0082 break; 0083 case QProcess::FailedToStart: 0084 default: 0085 mFailedToStart = true; 0086 break; 0087 } 0088 0089 qCWarning(AKONADICONTROL_LOG) << "ProcessControl: Application" << mApplication << "stopped unexpectedly (" << mProcess.errorString() << ")"; 0090 } 0091 0092 void ProcessControl::slotFinished(int exitCode, QProcess::ExitStatus exitStatus) 0093 { 0094 if (exitStatus == QProcess::CrashExit) { 0095 if (mPolicy == RestartOnCrash) { 0096 // don't try to start an unstartable application 0097 if (!mFailedToStart && mCrashCount <= s_maxCrashCount) { 0098 qCWarning(AKONADICONTROL_LOG, "Application '%s' crashed! %d restarts left.", qPrintable(mApplication), s_maxCrashCount - mCrashCount); 0099 start(); 0100 Q_EMIT restarted(); 0101 } else { 0102 if (mFailedToStart) { 0103 qCCritical(AKONADICONTROL_LOG, "Application '%s' failed to start!", qPrintable(mApplication)); 0104 } else { 0105 qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed too often. Giving up!", qPrintable(mApplication)); 0106 } 0107 mPolicy = StopOnCrash; 0108 Q_EMIT unableToStart(); 0109 return; 0110 } 0111 } else { 0112 qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed. No restart!", qPrintable(mApplication)); 0113 } 0114 } else { 0115 if (exitCode != 0) { 0116 qCWarning(AKONADICONTROL_LOG, 0117 "ProcessControl: Application '%s' returned with exit code %d (%s)", 0118 qPrintable(mApplication), 0119 exitCode, 0120 qPrintable(mProcess.errorString())); 0121 if (mPolicy == RestartOnCrash) { 0122 if (mCrashCount > s_maxCrashCount) { 0123 qCCritical(AKONADICONTROL_LOG) << mApplication << "crashed too often and will not be restarted!"; 0124 mPolicy = StopOnCrash; 0125 Q_EMIT unableToStart(); 0126 return; 0127 } 0128 ++mCrashCount; 0129 QTimer::singleShot(std::chrono::seconds{60}, this, &ProcessControl::resetCrashCount); 0130 if (!mFailedToStart) { // don't try to start an unstartable application 0131 start(); 0132 Q_EMIT restarted(); 0133 } 0134 } 0135 } else { 0136 if (mRestartOnceOnExit) { 0137 mRestartOnceOnExit = false; 0138 qCInfo(AKONADICONTROL_LOG, "Restarting application '%s'.", qPrintable(mApplication)); 0139 start(); 0140 } else { 0141 qCInfo(AKONADICONTROL_LOG, "Application '%s' exited normally...", qPrintable(mApplication)); 0142 Q_EMIT unableToStart(); 0143 } 0144 } 0145 } 0146 } 0147 0148 static bool listContains(const QStringList &list, const QString &pattern) 0149 { 0150 for (const QString &s : list) { 0151 if (s.contains(pattern)) { 0152 return true; 0153 } 0154 } 0155 return false; 0156 } 0157 0158 void ProcessControl::start() 0159 { 0160 // Prefer akonadiserver from the builddir 0161 mApplication = StandardDirs::findExecutable(mApplication); 0162 0163 #ifdef Q_OS_UNIX 0164 QString agentValgrind = akGetEnv("AKONADI_VALGRIND"); 0165 if (!agentValgrind.isEmpty() && (mApplication.contains(agentValgrind) || listContains(mArguments, agentValgrind))) { 0166 mArguments.prepend(mApplication); 0167 const QString originalArguments = mArguments.join(QString::fromLocal8Bit(" ")); 0168 mApplication = QString::fromLocal8Bit("valgrind"); 0169 0170 const QString valgrindSkin = akGetEnv("AKONADI_VALGRIND_SKIN", QString::fromLocal8Bit("memcheck")); 0171 mArguments.prepend(QLatin1StringView("--tool=") + valgrindSkin); 0172 0173 const QString valgrindOptions = akGetEnv("AKONADI_VALGRIND_OPTIONS"); 0174 if (!valgrindOptions.isEmpty()) { 0175 mArguments = valgrindOptions.split(QLatin1Char(' '), Qt::SkipEmptyParts) << mArguments; 0176 } 0177 0178 qCDebug(AKONADICONTROL_LOG); 0179 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0180 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrinding process" << originalArguments; 0181 if (!valgrindSkin.isEmpty()) { 0182 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrind skin:" << valgrindSkin; 0183 } 0184 if (!valgrindOptions.isEmpty()) { 0185 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Additional Valgrind options:" << valgrindOptions; 0186 } 0187 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0188 qCDebug(AKONADICONTROL_LOG); 0189 } 0190 0191 const QString agentHeaptrack = akGetEnv("AKONADI_HEAPTRACK"); 0192 if (!agentHeaptrack.isEmpty() && (mApplication.contains(agentHeaptrack) || listContains(mArguments, agentHeaptrack))) { 0193 mArguments.prepend(mApplication); 0194 const QString originalArguments = mArguments.join(QLatin1Char(' ')); 0195 mApplication = QStringLiteral("heaptrack"); 0196 0197 qCDebug(AKONADICONTROL_LOG); 0198 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0199 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Heaptracking process" << originalArguments; 0200 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0201 qCDebug(AKONADICONTROL_LOG); 0202 } 0203 0204 const QString agentPerf = akGetEnv("AKONADI_PERF"); 0205 if (!agentPerf.isEmpty() && (mApplication.contains(agentPerf) || listContains(mArguments, agentPerf))) { 0206 mArguments.prepend(mApplication); 0207 const QString originalArguments = mArguments.join(QLatin1Char(' ')); 0208 mApplication = QStringLiteral("perf"); 0209 0210 mArguments = QStringList{QStringLiteral("record"), QStringLiteral("--call-graph"), QStringLiteral("dwarf"), QStringLiteral("--")} + mArguments; 0211 0212 qCDebug(AKONADICONTROL_LOG); 0213 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0214 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Perf-recording process" << originalArguments; 0215 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0216 qCDebug(AKONADICONTROL_LOG); 0217 } 0218 #endif 0219 0220 mProcess.start(mApplication, mArguments); 0221 if (!mProcess.waitForStarted()) { 0222 qCWarning(AKONADICONTROL_LOG, "ProcessControl: Unable to start application '%s' (%s)", qPrintable(mApplication), qPrintable(mProcess.errorString())); 0223 Q_EMIT unableToStart(); 0224 return; 0225 } else { 0226 QString agentDebug = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT")); 0227 auto pid = mProcess.processId(); 0228 if (!agentDebug.isEmpty() && mApplication.contains(agentDebug)) { 0229 qCDebug(AKONADICONTROL_LOG); 0230 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0231 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Suspending process" << mApplication; 0232 #ifdef Q_OS_UNIX 0233 qCDebug(AKONADICONTROL_LOG) << "'gdb --pid" << pid << "' to debug"; 0234 qCDebug(AKONADICONTROL_LOG) << "'kill -SIGCONT" << pid << "' to continue"; 0235 kill(pid, SIGSTOP); 0236 #endif 0237 #ifdef Q_OS_WIN 0238 qCDebug(AKONADICONTROL_LOG) << "PID:" << pid; 0239 qCDebug(AKONADICONTROL_LOG) << "Process is waiting for a debugger..."; 0240 // the agent process will wait for a debugger to be attached in AgentBase::debugAgent() 0241 #endif 0242 qCDebug(AKONADICONTROL_LOG) << "============================================================"; 0243 qCDebug(AKONADICONTROL_LOG); 0244 } 0245 } 0246 } 0247 0248 void ProcessControl::resetCrashCount() 0249 { 0250 mCrashCount = 0; 0251 } 0252 0253 bool ProcessControl::isRunning() const 0254 { 0255 return mProcess.state() != QProcess::NotRunning; 0256 } 0257 0258 void ProcessControl::setShutdownTimeout(std::chrono::milliseconds timeout) 0259 { 0260 mShutdownTimeout = timeout; 0261 } 0262 0263 #include "moc_processcontrol.cpp"