411 lines
14 KiB
Plaintext
411 lines
14 KiB
Plaintext
// Copyright 2012 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/threading/platform_thread.h"
|
||
|
||
#import <Foundation/Foundation.h>
|
||
#include <mach/mach.h>
|
||
#include <mach/mach_time.h>
|
||
#include <mach/thread_policy.h>
|
||
#include <mach/thread_switch.h>
|
||
#include <stddef.h>
|
||
#include <sys/resource.h>
|
||
|
||
#include <algorithm>
|
||
#include <atomic>
|
||
|
||
#include "base/apple/foundation_util.h"
|
||
#include "base/apple/mach_logging.h"
|
||
#include "base/feature_list.h"
|
||
#include "base/lazy_instance.h"
|
||
#include "base/logging.h"
|
||
#include "base/mac/mac_util.h"
|
||
#include "base/metrics/histogram_functions.h"
|
||
#include "base/threading/thread_id_name_manager.h"
|
||
#include "base/threading/threading_features.h"
|
||
#include "build/blink_buildflags.h"
|
||
#include "build/build_config.h"
|
||
|
||
namespace base {
|
||
|
||
namespace {
|
||
NSString* const kRealtimePeriodNsKey = @"CrRealtimePeriodNsKey";
|
||
} // namespace
|
||
|
||
// If Foundation is to be used on more than one thread, it must know that the
|
||
// application is multithreaded. Since it's possible to enter Foundation code
|
||
// from threads created by pthread_thread_create, Foundation won't necessarily
|
||
// be aware that the application is multithreaded. Spawning an NSThread is
|
||
// enough to get Foundation to set up for multithreaded operation, so this is
|
||
// done if necessary before pthread_thread_create spawns any threads.
|
||
//
|
||
// https://developer.apple.com/documentation/foundation/nsthread/1410702-ismultithreaded
|
||
void InitThreading() {
|
||
static BOOL multithreaded = [NSThread isMultiThreaded];
|
||
if (!multithreaded) {
|
||
// +[NSObject class] is idempotent.
|
||
@autoreleasepool {
|
||
[NSThread detachNewThreadSelector:@selector(class)
|
||
toTarget:[NSObject class]
|
||
withObject:nil];
|
||
multithreaded = YES;
|
||
|
||
DCHECK([NSThread isMultiThreaded]);
|
||
}
|
||
}
|
||
}
|
||
|
||
TimeDelta PlatformThreadBase::Delegate::GetRealtimePeriod() {
|
||
return TimeDelta();
|
||
}
|
||
|
||
// static
|
||
void PlatformThreadBase::YieldCurrentThread() {
|
||
// Don't use sched_yield(), as it can lead to 10ms delays.
|
||
//
|
||
// This only depresses the thread priority for 1ms, which is more in line
|
||
// with what calling code likely wants. See this bug in webkit for context:
|
||
// https://bugs.webkit.org/show_bug.cgi?id=204871
|
||
mach_msg_timeout_t timeout_ms = 1;
|
||
thread_switch(MACH_PORT_NULL, SWITCH_OPTION_DEPRESS, timeout_ms);
|
||
}
|
||
|
||
// static
|
||
void PlatformThreadBase::SetName(const std::string& name) {
|
||
SetNameCommon(name);
|
||
|
||
// macOS does not expose the length limit of the name, so hardcode it.
|
||
const int kMaxNameLength = 63;
|
||
std::string shortened_name = name.substr(0, kMaxNameLength);
|
||
// pthread_setname() fails (harmlessly) in the sandbox, ignore when it does.
|
||
// See https://crbug.com/47058
|
||
pthread_setname_np(shortened_name.c_str());
|
||
}
|
||
|
||
// Whether optimized real-time thread config should be used for audio.
|
||
BASE_FEATURE(kOptimizedRealtimeThreadingMac,
|
||
#if BUILDFLAG(IS_MAC)
|
||
FEATURE_ENABLED_BY_DEFAULT
|
||
#else
|
||
FEATURE_DISABLED_BY_DEFAULT
|
||
#endif
|
||
);
|
||
|
||
namespace {
|
||
|
||
bool IsOptimizedRealtimeThreadingMacEnabled() {
|
||
return FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// Fine-tuning optimized real-time thread config:
|
||
// Whether or not the thread should be preemptible.
|
||
BASE_FEATURE_PARAM(bool,
|
||
kOptimizedRealtimeThreadingMacPreemptible,
|
||
&kOptimizedRealtimeThreadingMac,
|
||
"preemptible",
|
||
true);
|
||
// Portion of the time quantum the thread is expected to be busy, (0, 1].
|
||
BASE_FEATURE_PARAM(double,
|
||
kOptimizedRealtimeThreadingMacBusy,
|
||
&kOptimizedRealtimeThreadingMac,
|
||
"busy",
|
||
0.5);
|
||
// Maximum portion of the time quantum the thread is expected to be busy,
|
||
// (kOptimizedRealtimeThreadingMacBusy, 1].
|
||
BASE_FEATURE_PARAM(double,
|
||
kOptimizedRealtimeThreadingMacBusyLimit,
|
||
&kOptimizedRealtimeThreadingMac,
|
||
"busy_limit",
|
||
1.0);
|
||
|
||
namespace {
|
||
|
||
struct TimeConstraints {
|
||
bool preemptible{kOptimizedRealtimeThreadingMacPreemptible.default_value};
|
||
double busy{kOptimizedRealtimeThreadingMacBusy.default_value};
|
||
double busy_limit{kOptimizedRealtimeThreadingMacBusyLimit.default_value};
|
||
|
||
static TimeConstraints ReadFromFeatureParams() {
|
||
double busy_limit = kOptimizedRealtimeThreadingMacBusyLimit.Get();
|
||
return TimeConstraints{
|
||
kOptimizedRealtimeThreadingMacPreemptible.Get(),
|
||
std::min(busy_limit, kOptimizedRealtimeThreadingMacBusy.Get()),
|
||
busy_limit};
|
||
}
|
||
};
|
||
|
||
// Use atomics to access FeatureList values when setting up a thread, since
|
||
// there are cases when FeatureList initialization is not synchronized with
|
||
// PlatformThread creation.
|
||
std::atomic<bool> g_use_optimized_realtime_threading(
|
||
kOptimizedRealtimeThreadingMac.default_state == FEATURE_ENABLED_BY_DEFAULT);
|
||
std::atomic<TimeConstraints> g_time_constraints;
|
||
|
||
} // namespace
|
||
|
||
// static
|
||
void PlatformThreadApple::InitializeFeatures() {
|
||
g_time_constraints.store(TimeConstraints::ReadFromFeatureParams());
|
||
g_use_optimized_realtime_threading.store(
|
||
IsOptimizedRealtimeThreadingMacEnabled());
|
||
}
|
||
|
||
// static
|
||
void PlatformThreadApple::SetCurrentThreadRealtimePeriodValue(
|
||
TimeDelta realtime_period) {
|
||
if (g_use_optimized_realtime_threading.load()) {
|
||
NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey] =
|
||
@(realtime_period.InNanoseconds());
|
||
}
|
||
}
|
||
|
||
namespace {
|
||
|
||
TimeDelta GetCurrentThreadRealtimePeriod() {
|
||
NSNumber* period = apple::ObjCCast<NSNumber>(
|
||
NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey]);
|
||
|
||
return period ? Nanoseconds(period.longLongValue) : TimeDelta();
|
||
}
|
||
|
||
// Calculates time constraints for THREAD_TIME_CONSTRAINT_POLICY.
|
||
// |realtime_period| is used as a base if it's non-zero.
|
||
// Otherwise we fall back to empirical values.
|
||
thread_time_constraint_policy_data_t GetTimeConstraints(
|
||
TimeDelta realtime_period) {
|
||
thread_time_constraint_policy_data_t time_constraints;
|
||
mach_timebase_info_data_t tb_info;
|
||
mach_timebase_info(&tb_info);
|
||
|
||
if (!realtime_period.is_zero()) {
|
||
// Limit the lowest value to 2.9 ms we used to have historically. The lower
|
||
// the period, the more CPU frequency may go up, and we don't want to risk
|
||
// worsening the thermal situation.
|
||
uint32_t abs_realtime_period = saturated_cast<uint32_t>(
|
||
std::max(realtime_period.InNanoseconds(), 2900000LL) *
|
||
(double(tb_info.denom) / tb_info.numer));
|
||
TimeConstraints config = g_time_constraints.load();
|
||
time_constraints.period = abs_realtime_period;
|
||
time_constraints.constraint = std::min(
|
||
abs_realtime_period, uint32_t(abs_realtime_period * config.busy_limit));
|
||
time_constraints.computation =
|
||
std::min(time_constraints.constraint,
|
||
uint32_t(abs_realtime_period * config.busy));
|
||
time_constraints.preemptible = config.preemptible ? YES : NO;
|
||
return time_constraints;
|
||
}
|
||
|
||
// Empirical configuration.
|
||
|
||
// Define the guaranteed and max fraction of time for the audio thread.
|
||
// These "duty cycle" values can range from 0 to 1. A value of 0.5
|
||
// means the scheduler would give half the time to the thread.
|
||
// These values have empirically been found to yield good behavior.
|
||
// Good means that audio performance is high and other threads won't starve.
|
||
const double kGuaranteedAudioDutyCycle = 0.75;
|
||
const double kMaxAudioDutyCycle = 0.85;
|
||
|
||
// Define constants determining how much time the audio thread can
|
||
// use in a given time quantum. All times are in milliseconds.
|
||
|
||
// About 128 frames @44.1KHz
|
||
const double kTimeQuantum = 2.9;
|
||
|
||
// Time guaranteed each quantum.
|
||
const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
|
||
|
||
// Maximum time each quantum.
|
||
const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
|
||
|
||
// Get the conversion factor from milliseconds to absolute time
|
||
// which is what the time-constraints call needs.
|
||
double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000;
|
||
|
||
time_constraints.period = kTimeQuantum * ms_to_abs_time;
|
||
time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time;
|
||
time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time;
|
||
time_constraints.preemptible = 0;
|
||
return time_constraints;
|
||
}
|
||
|
||
// Enables time-constraint policy and priority suitable for low-latency,
|
||
// glitch-resistant audio.
|
||
void SetPriorityRealtimeAudio(TimeDelta realtime_period) {
|
||
// Increase thread priority to real-time.
|
||
|
||
// Please note that the thread_policy_set() calls may fail in
|
||
// rare cases if the kernel decides the system is under heavy load
|
||
// and is unable to handle boosting the thread priority.
|
||
// In these cases we just return early and go on with life.
|
||
|
||
mach_port_t mach_thread_id =
|
||
pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle());
|
||
|
||
// Make thread fixed priority.
|
||
thread_extended_policy_data_t policy;
|
||
policy.timeshare = 0; // Set to 1 for a non-fixed thread.
|
||
kern_return_t result = thread_policy_set(
|
||
mach_thread_id, THREAD_EXTENDED_POLICY,
|
||
reinterpret_cast<thread_policy_t>(&policy), THREAD_EXTENDED_POLICY_COUNT);
|
||
if (result != KERN_SUCCESS) {
|
||
MACH_DVLOG(1, result) << "thread_policy_set";
|
||
return;
|
||
}
|
||
|
||
// Set to relatively high priority.
|
||
thread_precedence_policy_data_t precedence;
|
||
precedence.importance = 63;
|
||
result = thread_policy_set(mach_thread_id, THREAD_PRECEDENCE_POLICY,
|
||
reinterpret_cast<thread_policy_t>(&precedence),
|
||
THREAD_PRECEDENCE_POLICY_COUNT);
|
||
if (result != KERN_SUCCESS) {
|
||
MACH_DVLOG(1, result) << "thread_policy_set";
|
||
return;
|
||
}
|
||
|
||
// Most important, set real-time constraints.
|
||
|
||
thread_time_constraint_policy_data_t time_constraints =
|
||
GetTimeConstraints(realtime_period);
|
||
|
||
result =
|
||
thread_policy_set(mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY,
|
||
reinterpret_cast<thread_policy_t>(&time_constraints),
|
||
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
|
||
MACH_DVLOG_IF(1, result != KERN_SUCCESS, result) << "thread_policy_set";
|
||
return;
|
||
}
|
||
|
||
std::optional<qos_class_t> ThreadTypeToQoSClass(ThreadType thread_type) {
|
||
switch (thread_type) {
|
||
case ThreadType::kBackground:
|
||
return QOS_CLASS_BACKGROUND;
|
||
case ThreadType::kUtility:
|
||
return QOS_CLASS_UTILITY;
|
||
case ThreadType::kDefault:
|
||
return QOS_CLASS_USER_INITIATED;
|
||
case ThreadType::kPresentation:
|
||
case ThreadType::kAudioProcessing:
|
||
return QOS_CLASS_USER_INTERACTIVE;
|
||
case ThreadType::kRealtimeAudio:
|
||
return std::nullopt;
|
||
}
|
||
}
|
||
|
||
} // anonymous namespace
|
||
|
||
// static
|
||
TimeDelta PlatformThreadApple::GetCurrentThreadRealtimePeriodForTest() {
|
||
return GetCurrentThreadRealtimePeriod();
|
||
}
|
||
|
||
// static
|
||
bool PlatformThreadBase::CanChangeThreadType(ThreadType from, ThreadType to) {
|
||
return true;
|
||
}
|
||
|
||
namespace internal {
|
||
|
||
void SetCurrentThreadTypeImpl(ThreadType thread_type,
|
||
MessagePumpType pump_type_hint) {
|
||
std::optional<qos_class_t> qos_class = ThreadTypeToQoSClass(thread_type);
|
||
|
||
if (qos_class) {
|
||
pthread_set_qos_class_self_np(*qos_class, 0);
|
||
} else {
|
||
CHECK_EQ(thread_type, ThreadType::kRealtimeAudio);
|
||
SetPriorityRealtimeAudio(GetCurrentThreadRealtimePeriod());
|
||
}
|
||
}
|
||
|
||
PlatformPriorityOverride SetThreadTypeOverride(
|
||
PlatformThreadHandle thread_handle,
|
||
ThreadType thread_type) {
|
||
std::optional<qos_class_t> qos_class = ThreadTypeToQoSClass(thread_type);
|
||
|
||
if (qos_class) {
|
||
return pthread_override_qos_class_start_np(thread_handle.platform_handle(),
|
||
*qos_class, 0);
|
||
}
|
||
|
||
return PlatformPriorityOverride();
|
||
}
|
||
|
||
void RemoveThreadTypeOverride(
|
||
PlatformThreadHandle thread_handle,
|
||
const PlatformPriorityOverride& priority_override_handle,
|
||
ThreadType initial_thread_type) {
|
||
if (priority_override_handle == nullptr) {
|
||
return;
|
||
}
|
||
pthread_override_qos_class_end_np(priority_override_handle);
|
||
}
|
||
|
||
} // namespace internal
|
||
|
||
// static
|
||
ThreadType PlatformThreadBase::GetCurrentEffectiveThreadTypeForTest() {
|
||
if ([NSThread.currentThread threadPriority] == 1.0) {
|
||
// Set to 1 for a non-fixed thread.)
|
||
return ThreadType::kRealtimeAudio;
|
||
}
|
||
|
||
qos_class_t qos_class = qos_class_self();
|
||
switch (qos_class) {
|
||
case QOS_CLASS_BACKGROUND:
|
||
return ThreadType::kBackground;
|
||
case QOS_CLASS_UTILITY:
|
||
return ThreadType::kUtility;
|
||
case QOS_CLASS_USER_INITIATED:
|
||
return ThreadType::kDefault;
|
||
case QOS_CLASS_USER_INTERACTIVE:
|
||
return ThreadType::kPresentation;
|
||
default:
|
||
return ThreadType::kDefault;
|
||
}
|
||
}
|
||
|
||
size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
|
||
#if BUILDFLAG(IS_IOS)
|
||
// For iOS 512kB (the default) isn't sufficient, but using the code
|
||
// for macOS below will return 8MB. So just be a little more conservative
|
||
// and return 1MB for now.
|
||
return 1024 * 1024;
|
||
#else
|
||
// The macOS default for a pthread stack size is 512kB.
|
||
// Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses
|
||
// DEFAULT_STACK_SIZE for this purpose.
|
||
//
|
||
// 512kB isn't quite generous enough for some deeply recursive threads that
|
||
// otherwise request the default stack size by specifying 0. Here, adopt
|
||
// glibc's behavior as on Linux, which is to use the current stack size
|
||
// limit (ulimit -s) as the default stack size. See
|
||
// glibc-2.11.1/nptl/nptl-init.c's __pthread_initialize_minimal_internal. To
|
||
// avoid setting the limit below the macOS default or the minimum usable
|
||
// stack size, these values are also considered. If any of these values
|
||
// can't be determined, or if stack size is unlimited (ulimit -s unlimited),
|
||
// stack_size is left at 0 to get the system default.
|
||
//
|
||
// macOS normally only applies ulimit -s to the main thread stack. On
|
||
// contemporary macOS and Linux systems alike, this value is generally 8MB
|
||
// or in that neighborhood.
|
||
size_t default_stack_size = 0;
|
||
struct rlimit stack_rlimit;
|
||
if (pthread_attr_getstacksize(&attributes, &default_stack_size) == 0 &&
|
||
getrlimit(RLIMIT_STACK, &stack_rlimit) == 0 &&
|
||
stack_rlimit.rlim_cur != RLIM_INFINITY) {
|
||
default_stack_size = std::max(
|
||
std::max(default_stack_size, static_cast<size_t>(PTHREAD_STACK_MIN)),
|
||
static_cast<size_t>(stack_rlimit.rlim_cur));
|
||
}
|
||
return default_stack_size;
|
||
#endif
|
||
}
|
||
|
||
void TerminateOnThread() {}
|
||
|
||
} // namespace base
|