Akonadi

processcontrol.cpp
1 /***************************************************************************
2  * Copyright (C) 2006 by Tobias Koenig <[email protected]> *
3  * *
4  * This program is free software; you can redistribute it and/or modify *
5  * it under the terms of the GNU Library General Public License as *
6  * published by the Free Software Foundation; either version 2 of the *
7  * License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU Library General Public *
15  * License along with this program; if not, write to the *
16  * Free Software Foundation, Inc., *
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18  ***************************************************************************/
19 
20 #include "processcontrol.h"
21 #include "akonadicontrol_debug.h"
22 
23 #include <shared/akapplication.h>
24 
25 #include <private/instance_p.h>
26 #include <private/standarddirs_p.h>
27 
28 #include <QTimer>
29 
30 #ifdef Q_OS_UNIX
31 #include <sys/types.h>
32 #include <signal.h>
33 #endif
34 
35 using namespace Akonadi;
36 using namespace std::chrono_literals;
37 
38 static const int s_maxCrashCount = 2;
39 
41  : QObject(parent)
42  , mPolicy(RestartOnCrash)
43  , mFailedToStart(false)
44  , mCrashCount(0)
45  , mRestartOnceOnExit(false)
46  , mShutdownTimeout(1s)
47 {
48  connect(&mProcess, &QProcess::errorOccurred,
49  this, &ProcessControl::slotError);
50  connect(&mProcess, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
51  this, &ProcessControl::slotFinished);
52  mProcess.setProcessChannelMode(QProcess::ForwardedChannels);
53 
54  if (Akonadi::Instance::hasIdentifier()) {
55  QProcessEnvironment env = mProcess.processEnvironment();
56  if (env.isEmpty()) {
58  }
59  env.insert(QStringLiteral("AKONADI_INSTANCE"), Akonadi::Instance::identifier());
60  mProcess.setProcessEnvironment(env);
61  }
62 }
63 
65 {
66  stop();
67 }
68 
69 void ProcessControl::start(const QString &application, const QStringList &arguments, CrashPolicy policy)
70 {
71  mFailedToStart = false;
72 
73  mApplication = application;
74  mArguments = arguments;
75  mPolicy = policy;
76 
77  start();
78 }
79 
81 {
82  mPolicy = policy;
83 }
84 
86 {
87  if (mProcess.state() != QProcess::NotRunning) {
88  mProcess.waitForFinished(mShutdownTimeout.count());
89  mProcess.terminate();
90  mProcess.waitForFinished(10000);
91  mProcess.kill();
92  }
93 }
94 
95 void ProcessControl::slotError(QProcess::ProcessError error)
96 {
97  switch (error) {
98  case QProcess::Crashed:
99  mCrashCount++;
100  // do nothing, we'll respawn in slotFinished
101  break;
102  case QProcess::FailedToStart:
103  default:
104  mFailedToStart = true;
105  break;
106  }
107 
108  qCWarning(AKONADICONTROL_LOG) << "ProcessControl: Application" << mApplication << "stopped unexpectedly (" << mProcess.errorString() << ")";
109 }
110 
111 void ProcessControl::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
112 {
113  if (exitStatus == QProcess::CrashExit) {
114  if (mPolicy == RestartOnCrash) {
115  // don't try to start an unstartable application
116  if (!mFailedToStart && mCrashCount <= s_maxCrashCount) {
117  qCWarning(AKONADICONTROL_LOG, "Application '%s' crashed! %d restarts left.", qPrintable(mApplication), s_maxCrashCount - mCrashCount);
118  start();
119  Q_EMIT restarted();
120  } else {
121  if (mFailedToStart) {
122  qCCritical(AKONADICONTROL_LOG, "Application '%s' failed to start!", qPrintable(mApplication));
123  } else {
124  qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed too often. Giving up!", qPrintable(mApplication));
125  }
126  mPolicy = StopOnCrash;
128  return;
129  }
130  } else {
131  qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed. No restart!", qPrintable(mApplication));
132  }
133  } else {
134  if (exitCode != 0) {
135  qCWarning(AKONADICONTROL_LOG, "ProcessControl: Application '%s' returned with exit code %d (%s)",
136  qPrintable(mApplication), exitCode, qPrintable(mProcess.errorString()));
137  if (mPolicy == RestartOnCrash) {
138  if (mCrashCount > s_maxCrashCount) {
139  qCCritical(AKONADICONTROL_LOG) << mApplication << "crashed too often and will not be restarted!";
140  mPolicy = StopOnCrash;
142  return;
143  }
144  ++mCrashCount;
145  QTimer::singleShot(60000, this, &ProcessControl::resetCrashCount);
146  if (!mFailedToStart) { // don't try to start an unstartable application
147  start();
148  Q_EMIT restarted();
149  }
150  }
151  } else {
152  if (mRestartOnceOnExit) {
153  mRestartOnceOnExit = false;
154  qCInfo(AKONADICONTROL_LOG, "Restarting application '%s'.", qPrintable(mApplication));
155  start();
156  } else {
157  qCInfo(AKONADICONTROL_LOG, "Application '%s' exited normally...", qPrintable(mApplication));
159  }
160  }
161  }
162 }
163 
164 static bool listContains(const QStringList &list, const QString &pattern)
165 {
166  for (const QString &s : list) {
167  if (s.contains(pattern)) {
168  return true;
169  }
170  }
171  return false;
172 }
173 
175 {
176  // Prefer akonadiserver from the builddir
177  mApplication = StandardDirs::findExecutable(mApplication);
178 
179 #ifdef Q_OS_UNIX
180  QString agentValgrind = akGetEnv("AKONADI_VALGRIND");
181  if (!agentValgrind.isEmpty() && (mApplication.contains(agentValgrind) || listContains(mArguments, agentValgrind))) {
182 
183  mArguments.prepend(mApplication);
184  const QString originalArguments = mArguments.join(QString::fromLocal8Bit(" "));
185  mApplication = QString::fromLocal8Bit("valgrind");
186 
187  const QString valgrindSkin = akGetEnv("AKONADI_VALGRIND_SKIN", QString::fromLocal8Bit("memcheck"));
188  mArguments.prepend(QLatin1String("--tool=") + valgrindSkin);
189 
190  const QString valgrindOptions = akGetEnv("AKONADI_VALGRIND_OPTIONS");
191  if (!valgrindOptions.isEmpty()) {
192  mArguments = valgrindOptions.split(QLatin1Char(' '), QString::SkipEmptyParts) << mArguments;
193  }
194 
195  qCDebug(AKONADICONTROL_LOG);
196  qCDebug(AKONADICONTROL_LOG) << "============================================================";
197  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrinding process" << originalArguments;
198  if (!valgrindSkin.isEmpty()) {
199  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrind skin:" << valgrindSkin;
200  }
201  if (!valgrindOptions.isEmpty()) {
202  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Additional Valgrind options:" << valgrindOptions;
203  }
204  qCDebug(AKONADICONTROL_LOG) << "============================================================";
205  qCDebug(AKONADICONTROL_LOG);
206  }
207 
208  const QString agentHeaptrack = akGetEnv("AKONADI_HEAPTRACK");
209  if (!agentHeaptrack.isEmpty() && (mApplication.contains(agentHeaptrack) || listContains(mArguments, agentHeaptrack))) {
210 
211  mArguments.prepend(mApplication);
212  const QString originalArguments = mArguments.join(QLatin1Char(' '));
213  mApplication = QStringLiteral("heaptrack");
214 
215  qCDebug(AKONADICONTROL_LOG);
216  qCDebug(AKONADICONTROL_LOG) << "============================================================";
217  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Heaptracking process" << originalArguments;
218  qCDebug(AKONADICONTROL_LOG) << "============================================================";
219  qCDebug(AKONADICONTROL_LOG);
220  }
221 
222  const QString agentPerf = akGetEnv("AKONADI_PERF");
223  if (!agentPerf.isEmpty() && (mApplication.contains(agentPerf) || listContains(mArguments, agentPerf))) {
224 
225  mArguments.prepend(mApplication);
226  const QString originalArguments = mArguments.join(QLatin1Char(' '));
227  mApplication = QStringLiteral("perf");
228 
229  mArguments = QStringList{ QStringLiteral("record"), QStringLiteral("--call-graph"), QStringLiteral("dwarf"), QStringLiteral("--") } + mArguments;
230 
231  qCDebug(AKONADICONTROL_LOG);
232  qCDebug(AKONADICONTROL_LOG) << "============================================================";
233  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Perf-recording process" << originalArguments;
234  qCDebug(AKONADICONTROL_LOG) << "============================================================";
235  qCDebug(AKONADICONTROL_LOG);
236  }
237 #endif
238 
239  mProcess.start(mApplication, mArguments);
240  if (!mProcess.waitForStarted()) {
241  qCWarning(AKONADICONTROL_LOG,
242  "ProcessControl: Unable to start application '%s' (%s)",
243  qPrintable(mApplication), qPrintable(mProcess.errorString()));
245  return;
246  } else {
247  QString agentDebug = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT"));
248  auto pid = mProcess.processId();
249  if (!agentDebug.isEmpty() && mApplication.contains(agentDebug)) {
250  qCDebug(AKONADICONTROL_LOG);
251  qCDebug(AKONADICONTROL_LOG) << "============================================================";
252  qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Suspending process" << mApplication;
253 #ifdef Q_OS_UNIX
254  qCDebug(AKONADICONTROL_LOG) << "'gdb --pid" << pid << "' to debug";
255  qCDebug(AKONADICONTROL_LOG) << "'kill -SIGCONT" << pid << "' to continue";
256  kill(pid, SIGSTOP);
257 #endif
258 #ifdef Q_OS_WIN
259  qCDebug(AKONADICONTROL_LOG) << "PID:" << pid;
260  qCDebug(AKONADICONTROL_LOG) << "Process is waiting for a debugger...";
261  // the agent process will wait for a debugger to be attached in AgentBase::debugAgent()
262 #endif
263  qCDebug(AKONADICONTROL_LOG) << "============================================================";
264  qCDebug(AKONADICONTROL_LOG);
265 
266  }
267  }
268 }
269 
270 void ProcessControl::resetCrashCount()
271 {
272  mCrashCount = 0;
273 }
274 
276 {
277  return mProcess.state() != QProcess::NotRunning;
278 }
279 
280 void ProcessControl::setShutdownTimeout(std::chrono::milliseconds timeout)
281 {
282  mShutdownTimeout = timeout;
283 }
qint64 processId() const const
void setCrashPolicy(CrashPolicy policy)
Sets the crash policy.
void kill()
QString errorString() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void stop()
Stops the currently running application.
void insert(const QString &name, const QString &value)
void restarted()
This signal is emitted when the server is restarted after a crash.
~ProcessControl()
Destroys the process control.
QString join(const QString &separator) const const
void terminate()
QProcessEnvironment processEnvironment() const const
void start()
Starts the process with the previously set application and arguments.
void errorOccurred(QProcess::ProcessError error)
QString fromLocal8Bit(const char *str, int size)
bool isEmpty() const const
void finished(int exitCode)
void unableToStart()
Emitted if the process could not be started since it terminated too often.
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool waitForStarted(int msecs)
void setProcessChannelMode(QProcess::ProcessChannelMode mode)
void setProcessEnvironment(const QProcessEnvironment &environment)
CrashPolicy
Theses enums describe the behaviour when the observed application crashed.
bool isEmpty() const const
ProcessControl(QObject *parent=nullptr)
Creates a new process control.
Helper integration between Akonadi and Qt.
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...
void prepend(const T &value)
bool isRunning() const
Returns true if the process is currently running.
QProcessEnvironment systemEnvironment()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QProcess::ProcessState state() const const
Q_EMITQ_EMIT
bool waitForFinished(int msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue May 26 2020 22:46:20 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.