~fboucault/unity-mir/dpr_rebase_qt_5.1

« back to all changes in this revision

Viewing changes to src/modules/Unity/Application/taskcontroller.cpp

Refactor Oom(Score)Adj to rely on process-cpp helper library. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
// Qt
26
26
#include <QStringList>
27
27
 
 
28
// Process C++
 
29
#include <core/posix/process.h>
 
30
#include <core/posix/this_process.h>
 
31
#include <core/posix/linux/proc/process/oom_adj.h>
 
32
#include <core/posix/linux/proc/process/oom_score_adj.h>
 
33
 
28
34
// STL
29
35
#include <mutex>
30
36
 
35
41
#include <signal.h>
36
42
#include <unistd.h>
37
43
 
38
 
// linux specific
39
 
#include <linux/oom.h>
40
 
 
41
 
namespace
42
 
{
43
 
/**
44
 
 * From man proc:
45
 
 *
46
 
 * This file can be used to adjust the score used to select which
47
 
 * process should be killed in an out-of-memory (OOM) situation.  The
48
 
 * kernel uses this value for a bit-shift opera‐ tion of the process's
49
 
 * oom_score value: valid values are in the range -16 to +15, plus the
50
 
 * special value -17, which disables OOM-killing altogether for this
51
 
 * process.  A posi‐ tive score increases the likelihood of this process
52
 
 * being killed by the OOM-killer; a negative score decreases the
53
 
 * likelihood.
54
 
 *
55
 
 * The default value for this file is 0; a new process inherits its
56
 
 * parent's oom_adj setting.  A process must be privileged
57
 
 * (CAP_SYS_RESOURCE) to update this file.
58
 
 
59
 
 * Since Linux 2.6.36, use of this file is deprecated in favor of
60
 
 * /proc/[pid]/oom_score_adj.
61
 
 */
62
 
struct OomAdjuster
63
 
{
64
 
    static int disableOomKillerValue()
65
 
    {
66
 
        return OOM_DISABLE;
67
 
    }
68
 
 
69
 
    static int minValue()
70
 
    {
71
 
        return OOM_ADJUST_MIN;
72
 
    }
73
 
 
74
 
    static int maxValue()
75
 
    {
76
 
        return OOM_ADJUST_MAX;
77
 
    }
78
 
 
79
 
    static OomAdjuster leastLikelyToBeKilled()
80
 
    {
81
 
        // By system default, we set the oom_score_adj of Unity8 to -10 (via lightdm).
82
 
        // As we want to avoid that any app's oom_score_adj or oom_adj is <= Unity8's oom_score_adj,
83
 
        // we choose a default value of -9 for oom_score_adj and 0 for oom_adj.
84
 
        static const int default_value = 0;
85
 
 
86
 
        return OomAdjuster(default_value);
87
 
    }
88
 
 
89
 
    static OomAdjuster mostLikelyToBeKilled()
90
 
    {
91
 
        return OomAdjuster(maxValue());
92
 
    }
93
 
 
94
 
    OomAdjuster(int value) : value(value)
95
 
    {
96
 
    }
97
 
 
98
 
    bool isValid() const
99
 
    {
100
 
        return !(value < disableOomKillerValue() || value > maxValue());
101
 
    }
102
 
 
103
 
    bool applyForPid(pid_t pid) const
104
 
    {
105
 
        auto fn = QString("/proc/%1/oom_adj").arg(pid);
106
 
        QFile file(fn);
107
 
 
108
 
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
109
 
            return false;
110
 
 
111
 
        QTextStream out(&file);
112
 
        out << value;
113
 
 
114
 
        return true;
115
 
    }
116
 
 
117
 
    int value;
118
 
};
119
 
 
120
 
/**
121
 
 * From man proc:
122
 
 *
123
 
 *  This file can be used to adjust the badness heuristic used to
124
 
 *  select which process gets killed in out-of-memory conditions.
125
 
 *
126
 
 *  The badness heuristic assigns a value to each candidate
127
 
 *  task ranging from 0 (never kill) to 1000 (always kill)
128
 
 *  to determine which process is targeted.  The units are
129
 
 *  roughly a proportion along that range of allowed memory
130
 
 *  the process may allocate from, based on an estimation of
131
 
 *  its current memory and swap use.  For example, if a task
132
 
 *  is using all allowed memory, its badness score will be
133
 
 *  1000.  If it is using half of its allowed memory, its
134
 
 *  score will be 500.
135
 
 *
136
 
 *  There is an additional factor included in the badness score: root
137
 
 *  processes are given 3% extra memory over other tasks.
138
 
 *
139
 
 *  The amount of "allowed" memory depends on the context in
140
 
 *  which the OOM-killer was called.  If it is due to the
141
 
 *  memory assigned to the allocating task's cpuset being
142
 
 *  exhausted, the allowed memory represents the set of mems
143
 
 *  assigned to that cpuset (see cpuset(7)).  If it is due
144
 
 *  to a mempolicy's node(s) being exhausted, the allowed
145
 
 *  memory represents the set of mempolicy nodes.  If it is
146
 
 *  due to a memory limit (or swap limit) being reached, the
147
 
 *  allowed memory is that configured limit.  Finally, if it
148
 
 *  is due to the entire system being out of memory, the
149
 
 *  allowed memory represents all allocatable resources.
150
 
 *
151
 
 *  The value of oom_score_adj is added to the badness score before it
152
 
 *  is used to determine which task to kill.  Acceptable values range
153
 
 *  from -1000 (OOM_SCORE_ADJ_MIN) to +1000 (OOM_SCORE_ADJ_MAX).  This
154
 
 *  allows user space to control the preference for OOM-killing,
155
 
 *  ranging from always preferring a certain task or completely
156
 
 *  disabling it from OOM- killing.  The lowest possible value, -1000,
157
 
 *  is equivalent to disabling OOM-killing entirely for that task,
158
 
 *  since it will always report a badness score of 0.
159
 
 *
160
 
 *  Consequently, it is very simple for user space to define the amount
161
 
 *  of memory to consider for each task.  Setting a oom_score_adj value
162
 
 *  of +500, for example, is roughly equiv‐ alent to allowing the
163
 
 *  remainder of tasks sharing the same system, cpuset, mempolicy, or
164
 
 *  memory controller resources to use at least 50% more memory.  A
165
 
 *  value of -500, on the other hand, would be roughly equivalent to
166
 
 *  discounting 50% of the task's allowed memory from being considered
167
 
 *  as scoring against the task.
168
 
 *
169
 
 *  For backward compatibility with previous kernels,
170
 
 *  /proc/[pid]/oom_adj can still be used to tune the badness score.
171
 
 *  Its value is scaled linearly with oom_score_adj.
172
 
 *
173
 
 *  Writing to /proc/[pid]/oom_score_adj or
174
 
 *  /proc/[pid]/oom_adj will change the other with its
175
 
 *  scaled value.
176
 
 */
177
 
struct OomScoreAdjuster
178
 
{
179
 
    static int disableOomKillerValue()
180
 
    {
181
 
        return OOM_SCORE_ADJ_MIN;
182
 
    }
183
 
 
184
 
    static int minValue()
185
 
    {
186
 
        return OOM_SCORE_ADJ_MIN;
187
 
    }
188
 
 
189
 
    static int maxValue()
190
 
    {
191
 
        return OOM_SCORE_ADJ_MAX;
192
 
    }
193
 
 
194
 
    static OomScoreAdjuster leastLikelyToBeKilled()
195
 
    {
196
 
        // By system default, we set the oom_score_adj of Unity8 to -10 (via lightdm).
197
 
        // As we want to avoid that any app's oom_score_adj is <= Unity8's oom_score_adj,
198
 
        // we choose a default value of -9, and a default increase of +1.
199
 
        static const int default_value = -9;
200
 
        static const int default_increase = 1;
201
 
        
202
 
 
203
 
        // We could be way more clever here if we knew the distribution
204
 
        // of oom_score_adj values of all app processes. However, we just
205
 
        // make sure that the process is not ignored by the oom killer for now.
206
 
        return OomScoreAdjuster(
207
 
            OomScoreAdjuster::thisProcess().isValid() ?
208
 
            OomScoreAdjuster::thisProcess().value + default_increase :
209
 
            default_value);
210
 
    }
211
 
 
212
 
    static OomScoreAdjuster mostLikelyToBeKilled()
213
 
    {
214
 
        // We avoid extremal values for oom_score_adj. For that, we
215
 
        // set it to 80% of the total available range. If we cannot
216
 
        // determine the oom_score_adj of the current process, i.e.,
217
 
        // Unity8, we substract -200 by default.
218
 
        static const float default_percentage = 0.8;
219
 
        static const int default_decrease = -200;
220
 
 
221
 
        return OomScoreAdjuster(
222
 
            OomScoreAdjuster::thisProcess().isValid() ?
223
 
            (maxValue() - OomScoreAdjuster::thisProcess().value) * default_percentage + OomScoreAdjuster::thisProcess().value :
224
 
            maxValue() - default_decrease);
225
 
    }
226
 
 
227
 
    static const OomScoreAdjuster& thisProcess()
228
 
    {
229
 
        // Initialize as invalid.
230
 
        static OomScoreAdjuster adjusterForThisProcess(minValue()-1);
231
 
 
232
 
        static std::once_flag query_once;
233
 
        std::call_once(
234
 
            query_once,
235
 
            []()
236
 
            {
237
 
                pid_t pid = getpid();
238
 
 
239
 
                auto fn = QString("/proc/%1/oom_score_adj").arg(pid);
240
 
                QFile file(fn);
241
 
 
242
 
                if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
243
 
                    return;
244
 
 
245
 
                QTextStream in(&file);
246
 
                int value; in >> value;
247
 
 
248
 
                adjusterForThisProcess.value = value;
249
 
            });
250
 
 
251
 
        return adjusterForThisProcess;
252
 
    }
253
 
 
254
 
    OomScoreAdjuster(int value) : value(value)
255
 
    {
256
 
    }
257
 
 
258
 
    bool isValid() const
259
 
    {
260
 
        return !(value < disableOomKillerValue() || value > maxValue());
261
 
    }
262
 
 
263
 
    bool applyForPid(pid_t pid) const
264
 
    {
265
 
        auto fn = QString("/proc/%1/oom_score_adj").arg(pid);
266
 
        QFile file(fn);
267
 
 
268
 
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
269
 
            return false;
270
 
 
271
 
        QTextStream out(&file);
272
 
        out << value;
273
 
 
274
 
        return true;
275
 
    }
276
 
 
277
 
    int value;
278
 
};
279
 
 
280
 
void ensureProcessIsUnlikelyToBeKilled(pid_t pid)
281
 
{
282
 
    if (!OomScoreAdjuster::leastLikelyToBeKilled().applyForPid(pid))
283
 
        if (!OomAdjuster::leastLikelyToBeKilled().applyForPid(pid))
284
 
            LOG("ensureProcessIsUnlikelyToBeKilled failed");
285
 
}
286
 
 
287
 
void ensureProcessIsLikelyToBeKilled(pid_t pid)
288
 
{
289
 
    if (!OomScoreAdjuster::mostLikelyToBeKilled().applyForPid(pid))
290
 
        if (!OomAdjuster::mostLikelyToBeKilled().applyForPid(pid))
291
 
            LOG("ensureProcessIsLikelyToBeKilled failed");
292
 
}
293
 
}
294
 
 
295
 
TaskController* TaskController::m_theTaskController = nullptr;
296
 
 
297
 
TaskController* TaskController::singleton()
298
 
{
299
 
    if (!m_theTaskController) {
300
 
        m_theTaskController = new TaskController();
301
 
    }
302
 
    return m_theTaskController;
303
 
}
304
 
 
305
 
TaskController::TaskController(QObject *parent) :
306
 
    QObject(parent)
307
 
{
308
 
    preStartCallback = [](const gchar * appId, gpointer userData) {
309
 
        Q_UNUSED(userData)
310
 
        Q_EMIT TaskController::singleton()->processStartReport(QString(appId), false);
311
 
    };
312
 
 
313
 
    startedCallback = [](const gchar * appId, gpointer userData) {
314
 
        Q_UNUSED(userData)
315
 
        pid_t pid = upstart_app_launch_get_primary_pid(appId);
316
 
        ensureProcessIsUnlikelyToBeKilled(pid);
317
 
    };
318
 
 
319
 
    stopCallback = [](const gchar * appId, gpointer userData) {
320
 
        Q_UNUSED(userData)
321
 
        Q_EMIT TaskController::singleton()->processStopped(QString(appId), false);
322
 
    };
323
 
 
324
 
    focusCallback = [](const gchar * appId, gpointer userData) {
325
 
        Q_UNUSED(userData)
326
 
        pid_t pid = upstart_app_launch_get_primary_pid(appId);
327
 
        ensureProcessIsUnlikelyToBeKilled(pid);
328
 
        Q_EMIT TaskController::singleton()->requestFocus(QString(appId));
329
 
    };
330
 
 
331
 
    resumeCallback = [](const gchar * appId, gpointer userData) {
332
 
        Q_UNUSED(userData)
333
 
        Q_EMIT TaskController::singleton()->requestResume(QString(appId));
334
 
    };
335
 
 
336
 
    failureCallback = [](const gchar * appId, upstart_app_launch_app_failed_t failureType, gpointer userData) {
337
 
        Q_UNUSED(userData)
338
 
        if (failureType == UPSTART_APP_LAUNCH_APP_FAILED_CRASH) {
339
 
            Q_EMIT TaskController::singleton()->processStopped(QString(appId), true);
340
 
        } else if (failureType == UPSTART_APP_LAUNCH_APP_FAILED_START_FAILURE) {
341
 
            Q_EMIT TaskController::singleton()->processStartReport(QString(appId), true);
342
 
        } else {
343
 
            LOG("TaskController: unknown failure type returned from upstart-app-launch");
344
 
        }
345
 
        Q_EMIT TaskController::singleton()->requestResume(QString(appId));
346
 
    };
347
 
 
348
 
    upstart_app_launch_observer_add_app_starting(preStartCallback, nullptr);
349
 
    upstart_app_launch_observer_add_app_started(startedCallback, nullptr);
350
 
    upstart_app_launch_observer_add_app_stop(stopCallback, nullptr);
351
 
    upstart_app_launch_observer_add_app_focus(focusCallback, nullptr);
352
 
    upstart_app_launch_observer_add_app_resume(resumeCallback, nullptr);
353
 
    upstart_app_launch_observer_add_app_failed(failureCallback, nullptr);
 
44
 
 
45
 
 
46
TaskController::TaskController(
 
47
        QObject* parent,
 
48
        const QSharedPointer<ApplicationController>& appController,
 
49
        const QSharedPointer<ProcessController>& processController) :
 
50
    QObject(parent),
 
51
    m_appController(appController),
 
52
    m_processController(processController)
 
53
{
 
54
    connect(m_appController.data(),
 
55
            &ApplicationController::applicationAboutToBeStarted,
 
56
            this,
 
57
            &TaskController::onApplicationAboutToBeStarted);
 
58
 
 
59
    connect(m_appController.data(),
 
60
            &ApplicationController::applicationStarted,
 
61
            this,
 
62
            &TaskController::onApplicationStarted);
 
63
 
 
64
    connect(m_appController.data(),
 
65
            &ApplicationController::applicationStopped,
 
66
            this,
 
67
            &TaskController::onApplicationStopped);
 
68
 
 
69
    connect(m_appController.data(),
 
70
            &ApplicationController::applicationFocusRequest,
 
71
            this,
 
72
            &TaskController::onApplicationFocusRequest);
 
73
 
 
74
    connect(m_appController.data(),
 
75
            &ApplicationController::applicationResumeRequest,
 
76
            this,
 
77
            &TaskController::onApplicationResumeRequest);
 
78
 
 
79
    connect(m_appController.data(),
 
80
            &ApplicationController::applicationError,
 
81
            this,
 
82
            &TaskController::onApplicationError);
354
83
}
355
84
 
356
85
TaskController::~TaskController()
357
86
{
358
 
    upstart_app_launch_observer_delete_app_starting(preStartCallback, nullptr);
359
 
    upstart_app_launch_observer_delete_app_started(startedCallback, nullptr);
360
 
    upstart_app_launch_observer_delete_app_stop(stopCallback, nullptr);
361
 
    upstart_app_launch_observer_delete_app_focus(focusCallback, nullptr);
362
 
    upstart_app_launch_observer_delete_app_resume(resumeCallback, nullptr);
363
 
    upstart_app_launch_observer_delete_app_failed(failureCallback, nullptr);
364
87
}
365
88
 
366
89
bool TaskController::start(const QString& appId, const QStringList& arguments)
367
90
{
368
91
    DLOG("TaskController::start appId='%s'", qPrintable(appId));
369
 
    gchar ** upstartArgs = nullptr;
370
 
    bool result = false;
371
 
 
372
 
    // Convert arguments QStringList into format suitable for upstart-app-launch
373
 
    upstartArgs = g_new0(gchar *, arguments.length());
374
 
 
375
 
    for (int i=0; i<arguments.length(); i++) {
376
 
        upstartArgs[i] = arguments.at(i).toLatin1().data();
377
 
    }
378
 
 
379
 
    result = upstart_app_launch_start_application(appId.toLatin1().constData(),
380
 
                                                  static_cast<const gchar * const *>(upstartArgs));
381
 
    g_free(upstartArgs);
382
 
 
383
 
    DLOG_IF(!result, "TaskController::startApplication appId='%s' FAILED", qPrintable(appId));
384
 
    return result;
 
92
    return m_appController->startApplicationWithAppIdAndArgs(appId, arguments);
385
93
}
386
94
 
387
95
bool TaskController::stop(const QString& appId)
388
96
{
389
97
    DLOG("TaskController::stop appId='%s'", qPrintable(appId));
390
 
    bool result = false;
391
 
 
392
 
    result = upstart_app_launch_stop_application(appId.toLatin1().constData());
393
 
 
 
98
    auto result = m_appController->stopApplicationWithAppId(appId);
394
99
    DLOG_IF(!result, "TaskController::stopApplication appId='%s' FAILED", qPrintable(appId));
395
100
    return result;
396
101
}
398
103
bool TaskController::appIdHasProcessId(const QString& appId, const quint64 pid)
399
104
{
400
105
    DLOG("TaskController::isApplicationPid appId='%s', pid=%lld", qPrintable(appId), pid);
401
 
    return upstart_app_launch_pid_in_app_id(pid, appId.toLatin1().constData());
 
106
    return m_appController->appIdHasProcessId(pid, appId);
402
107
}
403
108
 
404
109
bool TaskController::suspend(const QString& appId)
405
110
{
406
111
    DLOG("TaskController::suspend (this=%p, application=%p)", this, qPrintable(appId));
407
 
    pid_t pid = upstart_app_launch_get_primary_pid(appId.toLatin1().constData());
408
 
 
409
 
    ensureProcessIsLikelyToBeKilled(pid);
 
112
    pid_t pid = m_appController->primaryPidForAppId(appId);
 
113
    m_processController->oomController()->ensureProcessLikelyToBeKilled(pid);
410
114
 
411
115
    if (pid) {
412
116
        // We do assume that the app was launched by upstart and with that,
413
117
        // in its own process group. For that, we interpret the pid as pgid and
414
118
        // sigstop the complete process group on suspend.
415
 
        kill(-pid, SIGSTOP);
416
 
        return true;
 
119
        return m_processController->sigStopProcessGroupForPid(pid);
417
120
    } else {
418
121
        return false;
419
122
    }
422
125
bool TaskController::resume(const QString& appId)
423
126
{
424
127
    DLOG("TaskController::resume (this=%p, application=%p)", this, qPrintable(appId));
425
 
    pid_t pid = upstart_app_launch_get_primary_pid(appId.toLatin1().constData());
 
128
    pid_t pid = m_appController->primaryPidForAppId(appId);
426
129
 
427
 
    ensureProcessIsUnlikelyToBeKilled(pid);
 
130
    m_processController->oomController()->ensureProcessUnlikelyToBeKilled(pid);
428
131
 
429
132
    if (pid) {
430
133
        // We do assume that the app was launched by upstart and with that,
431
134
        // in its own process group. For that, we interpret the pid as pgid and
432
135
        // sigcont the complete process group on resume.
433
 
        kill(-pid, SIGCONT);
 
136
        return m_processController->sigContinueProcessGroupForPid(pid);
434
137
        return true;
435
138
    } else {
436
139
        return false;
437
140
    }
438
141
}
 
142
 
 
143
void TaskController::onApplicationAboutToBeStarted(const QString& id)
 
144
{
 
145
    Q_EMIT processStartReport(id, false);
 
146
}
 
147
 
 
148
void TaskController::onApplicationStarted(const QString& id)
 
149
{
 
150
    pid_t pid = m_appController->primaryPidForAppId(id);
 
151
    m_processController->oomController()->ensureProcessUnlikelyToBeKilled(pid);
 
152
}
 
153
 
 
154
void TaskController::onApplicationStopped(const QString& id)
 
155
{
 
156
    Q_EMIT processStopped(id, false);
 
157
}
 
158
 
 
159
void TaskController::onApplicationFocusRequest(const QString& id)
 
160
{
 
161
    pid_t pid = m_appController->primaryPidForAppId(id);
 
162
    m_processController->oomController()->ensureProcessUnlikelyToBeKilled(pid);
 
163
    Q_EMIT requestFocus(id);
 
164
}
 
165
 
 
166
void TaskController::onApplicationResumeRequest(const QString& id)
 
167
{
 
168
    Q_EMIT requestResume(id);
 
169
}
 
170
 
 
171
void TaskController::onApplicationError(const QString& id, ApplicationController::Error error)
 
172
{
 
173
    switch(error)
 
174
    {
 
175
    case ApplicationController::Error::APPLICATION_CRASHED:
 
176
        Q_EMIT processStopped(id, true);
 
177
        break;
 
178
    case ApplicationController::Error::APPLICATION_FAILED_TO_START:
 
179
        Q_EMIT processStartReport(id, true);
 
180
        break;
 
181
    }
 
182
 
 
183
    // Is this really the signal we want to emit?
 
184
    Q_EMIT requestResume(id);
 
185
}