blob: 632030d804c9c42aaad3113cb38543616a26027e [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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/feature_list.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/mach_logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/threading/thread_id_name_manager.h"
#include "base/threading/threading_features.h"
#include "build/build_config.h"
namespace base {
namespace {
NSString* const kThreadPriorityForTestKey = @"CrThreadPriorityForTestKey";
NSString* const kRealtimePeriodNsKey = @"CrRealtimePeriodNsKey";
} // namespace
// If Cocoa is to be used on more than one thread, it must know that the
// application is multithreaded. Since it's possible to enter Cocoa code
// from threads created by pthread_thread_create, Cocoa won't necessarily
// be aware that the application is multithreaded. Spawning an NSThread is
// enough to get Cocoa to set up for multithreaded operation, so this is done
// if necessary before pthread_thread_create spawns any threads.
//
// http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/chapter_4_section_4.html
void InitThreading() {
static BOOL multithreaded = [NSThread isMultiThreaded];
if (!multithreaded) {
// +[NSObject class] is idempotent.
[NSThread detachNewThreadSelector:@selector(class)
toTarget:[NSObject class]
withObject:nil];
multithreaded = YES;
DCHECK([NSThread isMultiThreaded]);
}
}
TimeDelta PlatformThread::Delegate::GetRealtimePeriod() {
return TimeDelta();
}
// static
void PlatformThread::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 PlatformThread::SetName(const std::string& name) {
ThreadIdNameManager::GetInstance()->SetName(name);
// Mac OS X 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 http://crbug.com/47058
pthread_setname_np(shortened_name.c_str());
}
// Whether optimized realt-time thread config should be used for audio.
const Feature kOptimizedRealtimeThreadingMac {
"OptimizedRealtimeThreadingMac",
#if BUILDFLAG(IS_MAC)
FEATURE_ENABLED_BY_DEFAULT
#else
FEATURE_DISABLED_BY_DEFAULT
#endif
};
namespace {
bool IsOptimizedRealtimeThreadingMacEnabled() {
#if BUILDFLAG(IS_MAC)
// There is some platform bug on 10.14.
if (mac::IsOS10_14())
return false;
#endif
return FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac);
}
} // namespace
// Fine-tuning optimized realt-time thread config:
// Whether or not the thread should be preeptible.
const FeatureParam<bool> kOptimizedRealtimeThreadingMacPreemptible{
&kOptimizedRealtimeThreadingMac, "preemptible", true};
// Portion of the time quantum the thread is expected to be busy, (0, 1].
const FeatureParam<double> kOptimizedRealtimeThreadingMacBusy{
&kOptimizedRealtimeThreadingMac, "busy", 0.5};
// Maximum portion of the time quantum the thread is expected to be busy,
// (kOptimizedRealtimeThreadingMacBusy, 1].
const FeatureParam<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 PlatformThread::InitializeOptimizedRealtimeThreadingFeature() {
// A DCHECK is triggered on FeatureList initialization if the state of a
// feature has been checked before. To avoid triggering this DCHECK in unit
// tests that call this before initializing the FeatureList, only check the
// state of the feature if the FeatureList is initialized.
if (FeatureList::GetInstance()) {
g_time_constraints.store(TimeConstraints::ReadFromFeatureParams());
g_use_optimized_realtime_threading.store(
IsOptimizedRealtimeThreadingMacEnabled());
}
}
// static
void PlatformThread::SetCurrentThreadRealtimePeriodValue(
TimeDelta realtime_period) {
if (g_use_optimized_realtime_threading.load()) {
[[NSThread currentThread] threadDictionary][kRealtimePeriodNsKey] =
@(realtime_period.InNanoseconds());
}
}
namespace {
TimeDelta GetCurrentThreadRealtimePeriod() {
NSNumber* period = mac::ObjCCast<NSNumber>(
[[NSThread currentThread] threadDictionary][kRealtimePeriodNsKey]);
return period ? Nanoseconds(period.longLongValue) : TimeDelta();
}
// Calculates time constrints 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-contraint 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";
UmaHistogramCustomMicrosecondsTimes(
"PlatformThread.Mac.AttemptedRealtimePeriod", realtime_period,
base::TimeDelta(), base::Milliseconds(100), 100);
if (result == KERN_SUCCESS) {
UmaHistogramCustomMicrosecondsTimes(
"PlatformThread.Mac.SucceededRealtimePeriod", realtime_period,
base::TimeDelta(), base::Milliseconds(100), 100);
}
return;
}
} // anonymous namespace
// static
bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
return true;
}
namespace internal {
void SetCurrentThreadTypeImpl(ThreadType thread_type,
MessagePumpType pump_type_hint) {
// Changing the priority of the main thread causes performance regressions.
// https://crbug.com/601270
if ([[NSThread currentThread] isMainThread]) {
DCHECK(thread_type == ThreadType::kDefault ||
thread_type == ThreadType::kCompositing);
return;
}
ThreadPriorityForTest priority = ThreadPriorityForTest::kNormal;
switch (thread_type) {
case ThreadType::kBackground:
priority = ThreadPriorityForTest::kBackground;
[[NSThread currentThread] setThreadPriority:0];
break;
case ThreadType::kDefault:
// TODO(1329208): Experiment with prioritizing kCompositing on Mac like on
// other platforms.
[[fallthrough]];
case ThreadType::kCompositing:
priority = ThreadPriorityForTest::kNormal;
[[NSThread currentThread] setThreadPriority:0.5];
break;
case ThreadType::kDisplayCritical: {
// Apple has suggested that insufficient priority may be the reason for
// Metal shader compilation hangs. A priority of 50 is higher than user
// input.
// https://crbug.com/974219.
priority = ThreadPriorityForTest::kDisplay;
[[NSThread currentThread] setThreadPriority:1.0];
sched_param param;
int policy;
pthread_t thread = pthread_self();
if (!pthread_getschedparam(thread, &policy, &param)) {
param.sched_priority = 50;
pthread_setschedparam(thread, policy, &param);
}
break;
}
case ThreadType::kRealtimeAudio:
priority = ThreadPriorityForTest::kRealtimeAudio;
SetPriorityRealtimeAudio(GetCurrentThreadRealtimePeriod());
DCHECK_EQ([[NSThread currentThread] threadPriority], 1.0);
break;
}
[[NSThread currentThread] threadDictionary][kThreadPriorityForTestKey] =
@(static_cast<int>(priority));
}
} // namespace internal
// static
ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
NSNumber* priority = base::mac::ObjCCast<NSNumber>(
[[NSThread currentThread] threadDictionary][kThreadPriorityForTestKey]);
if (!priority)
return ThreadPriorityForTest::kNormal;
ThreadPriorityForTest thread_priority =
static_cast<ThreadPriorityForTest>(priority.intValue);
DCHECK_GE(thread_priority, ThreadPriorityForTest::kBackground);
DCHECK_LE(thread_priority, ThreadPriorityForTest::kMaxValue);
return thread_priority;
}
size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
#if BUILDFLAG(IS_IOS)
return 0;
#else
// The Mac OS X 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 Mac OS X 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.
//
// Mac OS X normally only applies ulimit -s to the main thread stack. On
// contemporary OS X 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