processcontrol.cpp 11.4 KB
Newer Older
Till Adam's avatar
Till Adam committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/***************************************************************************
 *   Copyright (C) 2006 by Tobias Koenig <tokoe@kde.org>                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include "processcontrol.h"
21
#include "akonadicontrol_debug.h"
22

Daniel Vrátil's avatar
Daniel Vrátil committed
23
#include <chrono>
24
#include <shared/akapplication.h>
25

26
#include <private/instance_p.h>
27
#include <private/standarddirs_p.h>
Till Adam's avatar
Till Adam committed
28

Daniel Vrátil's avatar
Daniel Vrátil committed
29
#include <QTimer>
30
31
32
33
34

#ifdef Q_OS_UNIX
#include <sys/types.h>
#include <signal.h>
#endif
35

Till Adam's avatar
Till Adam committed
36
using namespace Akonadi;
Daniel Vrátil's avatar
Daniel Vrátil committed
37
using namespace std::chrono_literals;
Till Adam's avatar
Till Adam committed
38

Tobias Koenig's avatar
Tobias Koenig committed
39
40
static const int s_maxCrashCount = 2;

41
42
ProcessControl::ProcessControl(QObject *parent)
    : QObject(parent)
43
    , mPolicy(RestartOnCrash)
44
45
46
    , mFailedToStart(false)
    , mCrashCount(0)
    , mRestartOnceOnExit(false)
Daniel Vrátil's avatar
Daniel Vrátil committed
47
    , mShutdownTimeout(1s)
Till Adam's avatar
Till Adam committed
48
{
Laurent Montel's avatar
Laurent Montel committed
49
    connect(&mProcess, &QProcess::errorOccurred,
Laurent Montel's avatar
Laurent Montel committed
50
51
52
            this, &ProcessControl::slotError);
    connect(&mProcess, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),
            this, &ProcessControl::slotFinished);
53
54
    mProcess.setProcessChannelMode(QProcess::ForwardedChannels);

55
    if (Akonadi::Instance::hasIdentifier()) {
56
57
58
59
        QProcessEnvironment env = mProcess.processEnvironment();
        if (env.isEmpty()) {
            env = QProcessEnvironment::systemEnvironment();
        }
60
        env.insert(QStringLiteral("AKONADI_INSTANCE"), Akonadi::Instance::identifier());
61
        mProcess.setProcessEnvironment(env);
Guy Maurel's avatar
Guy Maurel committed
62
    }
Till Adam's avatar
Till Adam committed
63
64
65
66
}

ProcessControl::~ProcessControl()
{
67
    stop();
Till Adam's avatar
Till Adam committed
68
69
}

70
void ProcessControl::start(const QString &application, const QStringList &arguments, CrashPolicy policy)
Till Adam's avatar
Till Adam committed
71
{
72
    mFailedToStart = false;
Till Adam's avatar
Till Adam committed
73

74
75
76
    mApplication = application;
    mArguments = arguments;
    mPolicy = policy;
Till Adam's avatar
Till Adam committed
77

78
    start();
Till Adam's avatar
Till Adam committed
79
80
}

81
void ProcessControl::setCrashPolicy(CrashPolicy policy)
82
{
83
    mPolicy = policy;
84
85
}

Tobias Koenig's avatar
Tobias Koenig committed
86
87
void ProcessControl::stop()
{
88
    if (mProcess.state() != QProcess::NotRunning) {
Daniel Vrátil's avatar
Daniel Vrátil committed
89
        mProcess.waitForFinished(mShutdownTimeout.count());
90
        mProcess.terminate();
Daniel Vrátil's avatar
Daniel Vrátil committed
91
        mProcess.waitForFinished(std::chrono::milliseconds{10000}.count());
92
93
        mProcess.kill();
    }
Tobias Koenig's avatar
Tobias Koenig committed
94
95
}

96
void ProcessControl::slotError(QProcess::ProcessError error)
Till Adam's avatar
Till Adam committed
97
{
98
99
100
101
102
103
104
105
106
107
108
    switch (error) {
    case QProcess::Crashed:
        mCrashCount++;
        // do nothing, we'll respawn in slotFinished
        break;
    case QProcess::FailedToStart:
    default:
        mFailedToStart = true;
        break;
    }

109
    qCWarning(AKONADICONTROL_LOG) << "ProcessControl: Application" << mApplication << "stopped unexpectedly (" << mProcess.errorString() << ")";
Till Adam's avatar
Till Adam committed
110
111
}

112
void ProcessControl::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
Till Adam's avatar
Till Adam committed
113
{
114
115
116
117
    if (exitStatus == QProcess::CrashExit) {
        if (mPolicy == RestartOnCrash) {
            // don't try to start an unstartable application
            if (!mFailedToStart && mCrashCount <= s_maxCrashCount) {
118
                qCWarning(AKONADICONTROL_LOG, "Application '%s' crashed! %d restarts left.", qPrintable(mApplication), s_maxCrashCount - mCrashCount);
119
120
121
122
                start();
                Q_EMIT restarted();
            } else {
                if (mFailedToStart) {
123
                    qCCritical(AKONADICONTROL_LOG, "Application '%s' failed to start!", qPrintable(mApplication));
124
                } else {
125
                    qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed too often. Giving up!", qPrintable(mApplication));
126
127
128
129
130
                }
                mPolicy = StopOnCrash;
                Q_EMIT unableToStart();
                return;
            }
131
        } else {
132
            qCCritical(AKONADICONTROL_LOG, "Application '%s' crashed. No restart!", qPrintable(mApplication));
133
134
        }
    } else {
135
        if (exitCode != 0) {
136
            qCWarning(AKONADICONTROL_LOG, "ProcessControl: Application '%s' returned with exit code %d (%s)",
Laurent Montel's avatar
Laurent Montel committed
137
                      qPrintable(mApplication), exitCode, qPrintable(mProcess.errorString()));
138
139
            if (mPolicy == RestartOnCrash) {
                if (mCrashCount > s_maxCrashCount) {
140
                    qCCritical(AKONADICONTROL_LOG) << mApplication << "crashed too often and will not be restarted!";
141
142
143
144
145
                    mPolicy = StopOnCrash;
                    Q_EMIT unableToStart();
                    return;
                }
                ++mCrashCount;
Daniel Vrátil's avatar
Daniel Vrátil committed
146
                QTimer::singleShot(std::chrono::seconds{60}, this, &ProcessControl::resetCrashCount);
147
148
149
150
151
152
153
154
                if (!mFailedToStart) {   // don't try to start an unstartable application
                    start();
                    Q_EMIT restarted();
                }
            }
        } else {
            if (mRestartOnceOnExit) {
                mRestartOnceOnExit = false;
155
                qCInfo(AKONADICONTROL_LOG, "Restarting application '%s'.", qPrintable(mApplication));
156
157
                start();
            } else {
158
                qCInfo(AKONADICONTROL_LOG, "Application '%s' exited normally...", qPrintable(mApplication));
159
                Q_EMIT unableToStart();
160
            }
161
        }
Till Adam's avatar
Till Adam committed
162
163
164
    }
}

165
static bool listContains(const QStringList &list, const QString &pattern)
166
{
Laurent Montel's avatar
Laurent Montel committed
167
    for (const QString &s : list) {
168
169
170
        if (s.contains(pattern)) {
            return true;
        }
Guy Maurel's avatar
Guy Maurel committed
171
    }
172
    return false;
173
174
}

Till Adam's avatar
Till Adam committed
175
176
void ProcessControl::start()
{
177
178
179
    // Prefer akonadiserver from the builddir
    mApplication = StandardDirs::findExecutable(mApplication);

180
#ifdef Q_OS_UNIX
181
    QString agentValgrind = akGetEnv("AKONADI_VALGRIND");
182
    if (!agentValgrind.isEmpty() && (mApplication.contains(agentValgrind) || listContains(mArguments, agentValgrind))) {
183

184
185
186
        mArguments.prepend(mApplication);
        const QString originalArguments = mArguments.join(QString::fromLocal8Bit(" "));
        mApplication = QString::fromLocal8Bit("valgrind");
187

188
        const QString valgrindSkin = akGetEnv("AKONADI_VALGRIND_SKIN", QString::fromLocal8Bit("memcheck"));
189
        mArguments.prepend(QLatin1String("--tool=") + valgrindSkin);
190

191
        const QString valgrindOptions = akGetEnv("AKONADI_VALGRIND_OPTIONS");
192
        if (!valgrindOptions.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
193
194
195
196
197
198
199
            mArguments = valgrindOptions.split(QLatin1Char(' '),
                                   #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
                                               QString::SkipEmptyParts
                                   #else
                                               Qt::SkipEmptyParts
                                   #endif
                                               ) << mArguments;
200
        }
201

202
203
204
        qCDebug(AKONADICONTROL_LOG);
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrinding process" << originalArguments;
205
        if (!valgrindSkin.isEmpty()) {
206
            qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Valgrind skin:" << valgrindSkin;
207
208
        }
        if (!valgrindOptions.isEmpty()) {
209
            qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Additional Valgrind options:" << valgrindOptions;
210
        }
211
212
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG);
Guy Maurel's avatar
Guy Maurel committed
213
    }
214
215
216
217
218
219
220
221
222
223
224
225
226
227

    const QString agentHeaptrack = akGetEnv("AKONADI_HEAPTRACK");
    if (!agentHeaptrack.isEmpty() && (mApplication.contains(agentHeaptrack) || listContains(mArguments, agentHeaptrack))) {

        mArguments.prepend(mApplication);
        const QString originalArguments = mArguments.join(QLatin1Char(' '));
        mApplication = QStringLiteral("heaptrack");

        qCDebug(AKONADICONTROL_LOG);
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Heaptracking process" << originalArguments;
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG);
    }
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

    const QString agentPerf = akGetEnv("AKONADI_PERF");
    if (!agentPerf.isEmpty() && (mApplication.contains(agentPerf) || listContains(mArguments, agentPerf))) {

        mArguments.prepend(mApplication);
        const QString originalArguments = mArguments.join(QLatin1Char(' '));
        mApplication = QStringLiteral("perf");

        mArguments = QStringList{ QStringLiteral("record"), QStringLiteral("--call-graph"), QStringLiteral("dwarf"), QStringLiteral("--") } + mArguments;

        qCDebug(AKONADICONTROL_LOG);
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Perf-recording process" << originalArguments;
        qCDebug(AKONADICONTROL_LOG) << "============================================================";
        qCDebug(AKONADICONTROL_LOG);
    }
244
245
#endif

246
247
    mProcess.start(mApplication, mArguments);
    if (!mProcess.waitForStarted()) {
248
249
250
        qCWarning(AKONADICONTROL_LOG,
                  "ProcessControl: Unable to start application '%s' (%s)",
                  qPrintable(mApplication), qPrintable(mProcess.errorString()));
251
252
        Q_EMIT unableToStart();
        return;
253
    } else {
254
        QString agentDebug = QString::fromLocal8Bit(qgetenv("AKONADI_DEBUG_WAIT"));
255
        auto pid = mProcess.processId();
256
        if (!agentDebug.isEmpty() && mApplication.contains(agentDebug)) {
257
258
259
            qCDebug(AKONADICONTROL_LOG);
            qCDebug(AKONADICONTROL_LOG) << "============================================================";
            qCDebug(AKONADICONTROL_LOG) << "ProcessControl: Suspending process" << mApplication;
260
#ifdef Q_OS_UNIX
261
262
            qCDebug(AKONADICONTROL_LOG) << "'gdb --pid" << pid << "' to debug";
            qCDebug(AKONADICONTROL_LOG) << "'kill -SIGCONT" << pid << "' to continue";
263
            kill(pid, SIGSTOP);
264
265
#endif
#ifdef Q_OS_WIN
266
267
268
269
            qCDebug(AKONADICONTROL_LOG) << "PID:" << pid;
            qCDebug(AKONADICONTROL_LOG) << "Process is waiting for a debugger...";
            // the agent process will wait for a debugger to be attached in AgentBase::debugAgent()
#endif
270
271
            qCDebug(AKONADICONTROL_LOG) << "============================================================";
            qCDebug(AKONADICONTROL_LOG);
272

273
        }
274
    }
Till Adam's avatar
Till Adam committed
275
276
}

277
278
void ProcessControl::resetCrashCount()
{
279
    mCrashCount = 0;
280
281
}

282
283
bool ProcessControl::isRunning() const
{
284
    return mProcess.state() != QProcess::NotRunning;
285
286
}

Daniel Vrátil's avatar
Daniel Vrátil committed
287
void ProcessControl::setShutdownTimeout(std::chrono::milliseconds timeout)
288
{
Daniel Vrátil's avatar
Daniel Vrátil committed
289
    mShutdownTimeout = timeout;
290
}