| // 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 |