Akonadi

processcontrol.cpp
1 /***************************************************************************
2  * SPDX-FileCopyrightText: 2006 Tobias Koenig <[email protected]> *
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 
22 using namespace Akonadi;
23 using namespace std::chrono_literals;
24 
25 static 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()) {
36  QProcessEnvironment env = mProcess.processEnvironment();
37  if (env.isEmpty()) {
39  }
40  env.insert(QStringLiteral("AKONADI_INSTANCE"), Akonadi::Instance::identifier());
41  mProcess.setProcessEnvironment(env);
42  }
43 }
44 
46 {
47  stop();
48 }
49 
50 void 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 
76 void ProcessControl::slotError(QProcess::ProcessError error)
77 {
78  switch (error) {
79  case QProcess::Crashed:
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 
92 void 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();
100  Q_EMIT restarted();
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();
132  Q_EMIT restarted();
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 
148 static 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(QLatin1String("--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 
248 void ProcessControl::resetCrashCount()
249 {
250  mCrashCount = 0;
251 }
252 
254 {
255  return mProcess.state() != QProcess::NotRunning;
256 }
257 
258 void ProcessControl::setShutdownTimeout(std::chrono::milliseconds timeout)
259 {
260  mShutdownTimeout = timeout;
261 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QProcess::ProcessState state() const const
QString errorString() const const
void start()
Starts the process with the previously set application and arguments.
bool waitForFinished(int msecs)
Q_EMITQ_EMIT
void setProcessEnvironment(const QProcessEnvironment &environment)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void terminate()
QProcessEnvironment systemEnvironment()
CrashPolicy
These enums describe the behaviour when the observed application crashed.
ProcessControl(QObject *parent=nullptr)
Creates a new process control.
void unableToStart()
Emitted if the process could not be started since it terminated too often.
void finished(int exitCode)
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.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool isRunning() const
Returns true if the process is currently running.
void setProcessChannelMode(QProcess::ProcessChannelMode mode)
void prepend(const T &value)
QString fromLocal8Bit(const char *str, int size)
SkipEmptyParts
QProcessEnvironment processEnvironment() const const
bool isEmpty() const const
bool waitForStarted(int msecs)
void kill()
void insert(const QString &name, const QString &value)
QString join(const QString &separator) const const
~ProcessControl() override
Destroys the process control.
bool isEmpty() const const
void stop()
Stops the currently running application.
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void errorOccurred(QProcess::ProcessError error)
void restarted()
This signal is emitted when the server is restarted after a crash.
qint64 processId() const const
void setCrashPolicy(CrashPolicy policy)
Sets the crash policy.
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:52:16 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.