1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
base / process / process_mac.cc [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process/process.h"
#include <mach/mach.h>
#include <stddef.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include "base/apple/mach_logging.h"
#include "base/feature_list.h"
#include "base/memory/free_deleter.h"
namespace base {
namespace {
// Enables setting the task role of every child process to
// TASK_DEFAULT_APPLICATION.
BASE_FEATURE(kMacSetDefaultTaskRole,
"MacSetDefaultTaskRole",
FEATURE_ENABLED_BY_DEFAULT);
// Returns the `task_role_t` of the process whose task port is `task_port`.
std::optional<task_role_t> GetTaskCategoryPolicyRole(mach_port_t task_port) {
task_category_policy_data_t category_policy;
mach_msg_type_number_t task_info_count = TASK_CATEGORY_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&category_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_CATEGORY_POLICY";
return std::nullopt;
}
CHECK(!get_default);
return category_policy.role;
}
// Sets the task role for `task_port`.
bool SetTaskCategoryPolicy(mach_port_t task_port, task_role_t task_role) {
task_category_policy task_category_policy{.role = task_role};
kern_return_t result =
task_policy_set(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&task_category_policy),
TASK_CATEGORY_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_CATEGORY_POLICY";
return false;
}
return true;
}
// Taken from task_policy_private.h.
struct task_suppression_policy {
integer_t active;
integer_t lowpri_cpu;
integer_t timer_throttle;
integer_t disk_throttle;
integer_t cpu_limit;
integer_t suspend;
integer_t throughput_qos;
integer_t suppressed_cpu;
integer_t background_sockets;
integer_t reserved[7];
};
// Taken from task_policy_private.h.
#define TASK_SUPPRESSION_POLICY_COUNT \
((mach_msg_type_number_t)(sizeof(struct task_suppression_policy) / \
sizeof(integer_t)))
// Activates or deactivates the suppression policy to match the effect of App
// Nap.
bool SetTaskSuppressionPolicy(mach_port_t task_port, bool activate) {
task_suppression_policy suppression_policy = {
.active = activate,
.lowpri_cpu = activate,
.timer_throttle =
activate ? LATENCY_QOS_TIER_5 : LATENCY_QOS_TIER_UNSPECIFIED,
.disk_throttle = activate,
.cpu_limit = 0, /* unused */
.suspend = false, /* unused */
.throughput_qos = THROUGHPUT_QOS_TIER_UNSPECIFIED, /* unused */
.suppressed_cpu = activate,
.background_sockets = activate,
};
kern_return_t result =
task_policy_set(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
TASK_SUPPRESSION_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_SUPPRESSION_POLICY";
return false;
}
return true;
}
// Returns true if the task suppression policy is active for `task_port`.
bool IsTaskSuppressionPolicyActive(mach_port_t task_port) {
task_suppression_policy suppression_policy = {
.active = false,
};
mach_msg_type_number_t task_info_count = TASK_SUPPRESSION_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_SUPPRESSION_POLICY";
return false;
}
CHECK(!get_default);
// Only check the `active` property as it is sufficient to discern the state,
// even though other properties could be used.
return suppression_policy.active;
}
// Sets the task role and the suppression policy for `task_port`.
bool SetPriorityImpl(mach_port_t task_port,
task_role_t task_role,
bool activate_suppression_policy) {
// Do both operations, even if the first one fails.
bool succeeded = SetTaskCategoryPolicy(task_port, task_role);
succeeded &= SetTaskSuppressionPolicy(task_port, activate_suppression_policy);
return succeeded;
}
} // namespace
Time Process::CreationTime() const {
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, Pid()};
size_t len = 0;
if (sysctl(mib, std::size(mib), NULL, &len, NULL, 0) < 0)
return Time();
std::unique_ptr<struct kinfo_proc, base::FreeDeleter> proc(
static_cast<struct kinfo_proc*>(malloc(len)));
if (sysctl(mib, std::size(mib), proc.get(), &len, NULL, 0) < 0)
return Time();
return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime);
}
bool Process::CanSetPriority() {
return true;
}
Process::Priority Process::GetPriority(PortProvider* port_provider) const {
CHECK(IsValid());
CHECK(port_provider);
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
std::optional<task_role_t> task_role = GetTaskCategoryPolicyRole(task_port);
if (!task_role) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
bool is_suppression_policy_active = IsTaskSuppressionPolicyActive(task_port);
if (*task_role == TASK_BACKGROUND_APPLICATION &&
is_suppression_policy_active) {
return Priority::kBestEffort;
} else if (*task_role == TASK_BACKGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserVisible;
} else if (*task_role == TASK_FOREGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserBlocking;
}
// It is possible to get a different state very early in the process lifetime,
// before SetCurrentTaskDefaultRole() has been invoked. Assume highest
// priority then.
return Priority::kUserBlocking;
}
bool Process::SetPriority(PortProvider* port_provider, Priority priority) {
CHECK(IsValid());
CHECK(port_provider);
if (!CanSetPriority()) {
return false;
}
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
return false;
}
switch (priority) {
case Priority::kBestEffort:
// Activate the suppression policy.
// Note:
// App Nap keeps the task role to TASK_FOREGROUND_APPLICATION when it
// activates the suppression policy. Here TASK_BACKGROUND_APPLICATION is
// used instead to keep the kBestEffort role consistent with the value for
// kUserVisible (so that its is not greater than kUserVisible). This
// difference is unlikely to matter.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, true);
case Priority::kUserVisible:
// Set a task role with a lower priority than kUserBlocking, but do not
// activate the suppression policy.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, false);
case Priority::kUserBlocking:
default:
// Set the highest priority with the suppression policy inactive.
return SetPriorityImpl(task_port, TASK_FOREGROUND_APPLICATION, false);
}
}
// static
void Process::SetCurrentTaskDefaultRole() {
if (!base::FeatureList::IsEnabled(kMacSetDefaultTaskRole)) {
return;
}
SetTaskCategoryPolicy(mach_task_self(), TASK_FOREGROUND_APPLICATION);
// Set the QoS settings to tier 0, to match the default value given to App Nap
// enabled applications.
task_qos_policy task_qos_policy = {
.task_latency_qos_tier = LATENCY_QOS_TIER_0,
.task_throughput_qos_tier = THROUGHPUT_QOS_TIER_0,
};
task_policy_set(mach_task_self(), TASK_BASE_QOS_POLICY,
reinterpret_cast<task_policy_t>(&task_qos_policy),
TASK_QOS_POLICY_COUNT);
}
} // namespace base