Akonadi

processcontrol.cpp
1/***************************************************************************
2 * SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> *
3 * *
4 * SPDX-License-Identifier: LGPL-2.0-or-later *
5 ***************************************************************************/
6
7#include "processcontrol.h"
8#include "akonadicontrol_debug.h"
9
10#include "shared/akapplication.h"
11
12#include "private/instance_p.h"
13#include "private/standarddirs_p.h"
14
15#include <QTimer>
16
17#ifdef Q_OS_UNIX
18#include <signal.h>
19#include <sys/types.h>
20#endif
21
22using namespace Akonadi;
23using namespace std::chrono_literals;
24
25static const int s_maxCrashCount = 2;
26
28 : QObject(parent)
29 , mShutdownTimeout(1s)
30{
31 connect(&mProcess, &QProcess::errorOccurred, this, &ProcessControl::slotError);
32 connect(&mProcess, &QProcess::finished, this, &ProcessControl::slotFinished);
34
35 if (Akonadi::Instance::hasIdentifier()) {
37 if (env.isEmpty()) {
39 }
40 env.insert(QStringLiteral("AKONADI_INSTANCE"), Akonadi::Instance::identifier());
41 mProcess.setProcessEnvironment(env);
42 }
43}
44
49
50void ProcessControl::start(const QString &application, const QStringList &arguments, CrashPolicy policy)
51{
52 mFailedToStart = false;
53
54 mApplication = application;
55 mArguments = arguments;
56 mPolicy = policy;
57
58 start();
59}
60
62{
63 mPolicy = policy;
64}
65
67{
68 if (mProcess.state() != QProcess::NotRunning) {
69 mProcess.waitForFinished(mShutdownTimeout.count());
70 mProcess.terminate();
71 mProcess.waitForFinished(std::chrono::milliseconds{10000}.count());
72 mProcess.kill();
73 }
74}
75
76void ProcessControl::slotError(QProcess::ProcessError error)
77{
78 switch (error) {
80 mCrashCount++;
81 // do nothing, we'll respawn in slotFinished
82 break;
84 default:
85 mFailedToStart = true;
86 break;
87 }
88
89 qCWarning(AKONADICONTROL_LOG) << "ProcessControl: Application" << mApplication << "stopped unexpectedly (" << mProcess.errorString() << ")";
90}
91
92void ProcessControl::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
93{
94 if (exitStatus == QProcess::CrashExit) {
95 if (mPolicy == RestartOnCrash) {
96 // don't try to start an unstartable application
97 if (!mFailedToStart && mCrashCount <= s_maxCrashCount) {
98 qCWarning(AKONADICONTROL_LOG, "Application '%s' crashed! %d restarts left.", qPrintable(mApplication), s_maxCrashCount - mCrashCount);
99 start();
101 } else {
102 if (mFailedToStart) {
103 qCCritical(AKONADICONTROL_LOG, "Application '%s' failed to start!", qPrintable(mApplication));
104 } else {
105 qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed too often. Giving up!", qPrintable(mApplication));
106 }
107 mPolicy = StopOnCrash;
109 return;
110 }
111 } else {
112 qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed. No restart!", qPrintable(mApplication));
113 }
114 } else {
115 if (exitCode != 0) {
116 qCWarning(AKONADICONTROL_LOG,
117 "ProcessControl: Application '%s' returned with exit code %d (%s)",
118 qPrintable(mApplication),
119 exitCode,
120 qPrintable(mProcess.errorString()));
121 if (mPolicy == RestartOnCrash) {
122 if (mCrashCount > s_maxCrashCount) {
123 qCCritical(AKONADICONTROL_LOG) << mApplication << "crashed too often and will not be restarted!";
124 mPolicy = StopOnCrash;
126 return;
127 }
128 ++mCrashCount;
129 QTimer::singleShot(std::chrono::seconds{60}, this, &ProcessControl::resetCrashCount);
130 if (!mFailedToStart) { // don't try to start an unstartable application
131 start();
133 }
134 }
135 } else {
136 if (mRestartOnceOnExit) {
137 mRestartOnceOnExit = false;
138 qCInfo(AKONADICONTROL_LOG, "Restarting application '%s'.", qPrintable(mApplication));
139 start();
140 } else {
141 qCInfo(AKONADICONTROL_LOG, "Application '%s' exited normally...", qPrintable(mApplication));
143 }
144 }
145 }
146}
147
148static bool listContains(const QStringList &list, const QString &pattern)
149{
150 for (const QString &s : list) {
151 if (s.contains(pattern)) {
152 return true;
153 }
154 }
155 return false;
156}
157
159{
160 // Prefer akonadiserver from the builddir
161 mApplication = StandardDirs::findExecutable(mApplication);
162
163#ifdef Q_OS_UNIX
164 QString agentValgrind = akGetEnv("AKONADI_VALGRIND");
165 if (!agentValgrind.isEmpty() && (mApplication.contains(agentValgrind) || listContains(mArguments, agentValgrind))) {
166 mArguments.prepend(mApplication);
167 const QString originalArguments = mArguments.join(QString::fromLocal8Bit(" "));
168 mApplication = QString::fromLocal8Bit("valgrind");
169
170 const QString valgrindSkin = akGetEnv("AKONADI_VALGRIND_SKIN", QString::fromLocal8Bit("memcheck"));
171 mArguments.prepend(QLatin1StringView("--tool=") + valgrindSkin);
172
173 const QString valgrindOptions = akGetEnv("AKONADI_VALGRIND_OPTIONS");
174 if (!valgrindOptions.isEmpty()) {
175 mArguments = valgrindOptions.split(QLatin1Char(' '), Qt::SkipEmptyParts) << mArguments;
176 }
177
178 qCDebug(AKONADICONTROL_LOG);
179 qCDebug(AKONADICONTROL_LOG) << "============================================================";
180 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrinding process" << originalArguments;
181 if (!valgrindSkin.isEmpty()) {
182 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrind skin:" << valgrindSkin;
183 }
184 if (!valgrindOptions.isEmpty()) {
185 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Additional Valgrind options:" << valgrindOptions;
186 }
187 qCDebug(AKONADICONTROL_LOG) << "============================================================";
188 qCDebug(AKONADICONTROL_LOG);
189 }
190
191 const QString agentHeaptrack = akGetEnv("AKONADI_HEAPTRACK");
192 if (!agentHeaptrack.isEmpty() && (mApplication.contains(agentHeaptrack) || listContains(mArguments, agentHeaptrack))) {
193 mArguments.prepend(mApplication);
194 const QString originalArguments = mArguments.join(QLatin1Char(' '));
195 mApplication = QStringLiteral("heaptrack");
196
197 qCDebug(AKONADICONTROL_LOG);
198 qCDebug(AKONADICONTROL_LOG) << "============================================================";
199 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Heaptracking process" << originalArguments;
200 qCDebug(AKONADICONTROL_LOG) << "============================================================";
201 qCDebug(AKONADICONTROL_LOG);
202 }
203
204 const QString agentPerf = akGetEnv("AKONADI_PERF");
205 if (!agentPerf.isEmpty() && (mApplication.contains(agentPerf) || listContains(mArguments, agentPerf))) {
206 mArguments.prepend(mApplication);
207 const QString originalArguments = mArguments.join(QLatin1Char(' '));
208 mApplication = QStringLiteral("perf");
209
210 mArguments = QStringList{QStringLiteral("record"), QStringLiteral("--call-graph"), QStringLiteral("dwarf"), QStringLiteral("--")} + mArguments;
211
212 qCDebug(AKONADICONTROL_LOG);
213 qCDebug(AKONADICONTROL_LOG) << "============================================================";
214 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Perf-recording process" << originalArguments;
215 qCDebug(AKONADICONTROL_LOG) << "============================================================";
216 qCDebug(AKONADICONTROL_LOG);
217 }
218#endif
219
220 mProcess.start(mApplication, mArguments);
221 if (!mProcess.waitForStarted()) {
222 qCWarning(AKONADICONTROL_LOG, "ProcessControl: Unable to start application '%s' (%s)", qPrintable(mApplication), qPrintable(mProcess.errorString()));
224 return;
225 } else {
226 QString agentDebug = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT"));
227 auto pid = mProcess.processId();
228 if (!agentDebug.isEmpty() && mApplication.contains(agentDebug)) {
229 qCDebug(AKONADICONTROL_LOG);
230 qCDebug(AKONADICONTROL_LOG) << "============================================================";
231 qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Suspending process" << mApplication;
232#ifdef Q_OS_UNIX
233 qCDebug(AKONADICONTROL_LOG) << "'gdb --pid" << pid << "' to debug";
234 qCDebug(AKONADICONTROL_LOG) << "'kill -SIGCONT" << pid << "' to continue";
235 kill(pid, SIGSTOP);
236#endif
237#ifdef Q_OS_WIN
238 qCDebug(AKONADICONTROL_LOG) << "PID:" << pid;
239 qCDebug(AKONADICONTROL_LOG) << "Process is waiting for a debugger...";
240 // the agent process will wait for a debugger to be attached in AgentBase::debugAgent()
241#endif
242 qCDebug(AKONADICONTROL_LOG) << "============================================================";
243 qCDebug(AKONADICONTROL_LOG);
244 }
245 }
246}
247
248void ProcessControl::resetCrashCount()
249{
250 mCrashCount = 0;
251}
252
254{
255 return mProcess.state() != QProcess::NotRunning;
256}
257
258void ProcessControl::setShutdownTimeout(std::chrono::milliseconds timeout)
259{
260 mShutdownTimeout = timeout;
261}
262
263#include "moc_processcontrol.cpp"
~ProcessControl() override
Destroys the process control.
void setCrashPolicy(CrashPolicy policy)
Sets the crash policy.
void start()
Starts the process with the previously set application and arguments.
void stop()
Stops the currently running application.
void unableToStart()
Emitted if the process could not be started since it terminated too often.
bool isRunning() const
Returns true if the process is currently running.
void setShutdownTimeout(std::chrono::milliseconds timeout)
Sets the time (in msecs) we wait for the process to shut down before we send terminate/kill signals.
ProcessControl(QObject *parent=nullptr)
Creates a new process control.
CrashPolicy
These enums describe the behaviour when the observed application crashed.
void restarted()
This signal is emitted when the server is restarted after a crash.
Helper integration between Akonadi and Qt.
QString errorString() const const
void prepend(parameter_type value)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void errorOccurred(QProcess::ProcessError error)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void kill()
QProcessEnvironment processEnvironment() const const
qint64 processId() const const
void setProcessChannelMode(ProcessChannelMode mode)
void setProcessEnvironment(const QProcessEnvironment &environment)
void start(OpenMode mode)
QProcess::ProcessState state() const const
void terminate()
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
void insert(const QProcessEnvironment &e)
bool isEmpty() const const
QProcessEnvironment systemEnvironment()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:29 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.