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