1// Copyright 2012 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/threading/platform_thread.h" 6 7#import <Foundation/Foundation.h> 8#include <mach/mach.h> 9#include <mach/mach_time.h> 10#include <mach/thread_policy.h> 11#include <mach/thread_switch.h> 12#include <stddef.h> 13#include <sys/resource.h> 14 15#include <algorithm> 16#include <atomic> 17 18#include "base/apple/foundation_util.h" 19#include "base/apple/mach_logging.h" 20#include "base/feature_list.h" 21#include "base/lazy_instance.h" 22#include "base/logging.h" 23#include "base/mac/mac_util.h" 24#include "base/metrics/histogram_functions.h" 25#include "base/threading/thread_id_name_manager.h" 26#include "base/threading/threading_features.h" 27#include "build/blink_buildflags.h" 28#include "build/build_config.h" 29 30namespace base { 31 32namespace { 33NSString* const kRealtimePeriodNsKey = @"CrRealtimePeriodNsKey"; 34} // namespace 35 36// If Foundation is to be used on more than one thread, it must know that the 37// application is multithreaded. Since it's possible to enter Foundation code 38// from threads created by pthread_thread_create, Foundation won't necessarily 39// be aware that the application is multithreaded. Spawning an NSThread is 40// enough to get Foundation to set up for multithreaded operation, so this is 41// done if necessary before pthread_thread_create spawns any threads. 42// 43// https://developer.apple.com/documentation/foundation/nsthread/1410702-ismultithreaded 44void InitThreading() { 45 static BOOL multithreaded = [NSThread isMultiThreaded]; 46 if (!multithreaded) { 47 // +[NSObject class] is idempotent. 48 @autoreleasepool { 49 [NSThread detachNewThreadSelector:@selector(class) 50 toTarget:[NSObject class] 51 withObject:nil]; 52 multithreaded = YES; 53 54 DCHECK([NSThread isMultiThreaded]); 55 } 56 } 57} 58 59TimeDelta PlatformThreadBase::Delegate::GetRealtimePeriod() { 60 return TimeDelta(); 61} 62 63// static 64void PlatformThreadBase::YieldCurrentThread() { 65 // Don't use sched_yield(), as it can lead to 10ms delays. 66 // 67 // This only depresses the thread priority for 1ms, which is more in line 68 // with what calling code likely wants. See this bug in webkit for context: 69 // https://bugs.webkit.org/show_bug.cgi?id=204871 70 mach_msg_timeout_t timeout_ms = 1; 71 thread_switch(MACH_PORT_NULL, SWITCH_OPTION_DEPRESS, timeout_ms); 72} 73 74// static 75void PlatformThreadBase::SetName(const std::string& name) { 76 SetNameCommon(name); 77 78 // macOS does not expose the length limit of the name, so hardcode it. 79 const int kMaxNameLength = 63; 80 std::string shortened_name = name.substr(0, kMaxNameLength); 81 // pthread_setname() fails (harmlessly) in the sandbox, ignore when it does. 82 // See https://crbug.com/47058 83 pthread_setname_np(shortened_name.c_str()); 84} 85 86// Whether optimized real-time thread config should be used for audio. 87BASE_FEATURE(kOptimizedRealtimeThreadingMac, 88 "OptimizedRealtimeThreadingMac", 89#if BUILDFLAG(IS_MAC) 90 FEATURE_ENABLED_BY_DEFAULT 91#else 92 FEATURE_DISABLED_BY_DEFAULT 93#endif 94); 95 96namespace { 97 98bool IsOptimizedRealtimeThreadingMacEnabled() { 99 return FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac); 100} 101 102} // namespace 103 104// Fine-tuning optimized real-time thread config: 105// Whether or not the thread should be preemptible. 106const FeatureParam<bool> kOptimizedRealtimeThreadingMacPreemptible{ 107 &kOptimizedRealtimeThreadingMac, "preemptible", true}; 108// Portion of the time quantum the thread is expected to be busy, (0, 1]. 109const FeatureParam<double> kOptimizedRealtimeThreadingMacBusy{ 110 &kOptimizedRealtimeThreadingMac, "busy", 0.5}; 111// Maximum portion of the time quantum the thread is expected to be busy, 112// (kOptimizedRealtimeThreadingMacBusy, 1]. 113const FeatureParam<double> kOptimizedRealtimeThreadingMacBusyLimit{ 114 &kOptimizedRealtimeThreadingMac, "busy_limit", 1.0}; 115 116namespace { 117 118struct TimeConstraints { 119 bool preemptible{kOptimizedRealtimeThreadingMacPreemptible.default_value}; 120 double busy{kOptimizedRealtimeThreadingMacBusy.default_value}; 121 double busy_limit{kOptimizedRealtimeThreadingMacBusyLimit.default_value}; 122 123 static TimeConstraints ReadFromFeatureParams() { 124 double busy_limit = kOptimizedRealtimeThreadingMacBusyLimit.Get(); 125 return TimeConstraints{ 126 kOptimizedRealtimeThreadingMacPreemptible.Get(), 127 std::min(busy_limit, kOptimizedRealtimeThreadingMacBusy.Get()), 128 busy_limit}; 129 } 130}; 131 132// Use atomics to access FeatureList values when setting up a thread, since 133// there are cases when FeatureList initialization is not synchronized with 134// PlatformThread creation. 135std::atomic<bool> g_use_optimized_realtime_threading( 136 kOptimizedRealtimeThreadingMac.default_state == FEATURE_ENABLED_BY_DEFAULT); 137std::atomic<TimeConstraints> g_time_constraints; 138 139} // namespace 140 141// static 142void PlatformThreadApple::InitializeFeatures() { 143 g_time_constraints.store(TimeConstraints::ReadFromFeatureParams()); 144 g_use_optimized_realtime_threading.store( 145 IsOptimizedRealtimeThreadingMacEnabled()); 146} 147 148// static 149void PlatformThreadApple::SetCurrentThreadRealtimePeriodValue( 150 TimeDelta realtime_period) { 151 if (g_use_optimized_realtime_threading.load()) { 152 NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey] = 153 @(realtime_period.InNanoseconds()); 154 } 155} 156 157namespace { 158 159TimeDelta GetCurrentThreadRealtimePeriod() { 160 NSNumber* period = apple::ObjCCast<NSNumber>( 161 NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey]); 162 163 return period ? Nanoseconds(period.longLongValue) : TimeDelta(); 164} 165 166// Calculates time constraints for THREAD_TIME_CONSTRAINT_POLICY. 167// |realtime_period| is used as a base if it's non-zero. 168// Otherwise we fall back to empirical values. 169thread_time_constraint_policy_data_t GetTimeConstraints( 170 TimeDelta realtime_period) { 171 thread_time_constraint_policy_data_t time_constraints; 172 mach_timebase_info_data_t tb_info; 173 mach_timebase_info(&tb_info); 174 175 if (!realtime_period.is_zero()) { 176 // Limit the lowest value to 2.9 ms we used to have historically. The lower 177 // the period, the more CPU frequency may go up, and we don't want to risk 178 // worsening the thermal situation. 179 uint32_t abs_realtime_period = saturated_cast<uint32_t>( 180 std::max(realtime_period.InNanoseconds(), 2900000LL) * 181 (double(tb_info.denom) / tb_info.numer)); 182 TimeConstraints config = g_time_constraints.load(); 183 time_constraints.period = abs_realtime_period; 184 time_constraints.constraint = std::min( 185 abs_realtime_period, uint32_t(abs_realtime_period * config.busy_limit)); 186 time_constraints.computation = 187 std::min(time_constraints.constraint, 188 uint32_t(abs_realtime_period * config.busy)); 189 time_constraints.preemptible = config.preemptible ? YES : NO; 190 return time_constraints; 191 } 192 193 // Empirical configuration. 194 195 // Define the guaranteed and max fraction of time for the audio thread. 196 // These "duty cycle" values can range from 0 to 1. A value of 0.5 197 // means the scheduler would give half the time to the thread. 198 // These values have empirically been found to yield good behavior. 199 // Good means that audio performance is high and other threads won't starve. 200 const double kGuaranteedAudioDutyCycle = 0.75; 201 const double kMaxAudioDutyCycle = 0.85; 202 203 // Define constants determining how much time the audio thread can 204 // use in a given time quantum. All times are in milliseconds. 205 206 // About 128 frames @44.1KHz 207 const double kTimeQuantum = 2.9; 208 209 // Time guaranteed each quantum. 210 const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; 211 212 // Maximum time each quantum. 213 const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; 214 215 // Get the conversion factor from milliseconds to absolute time 216 // which is what the time-constraints call needs. 217 double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000; 218 219 time_constraints.period = kTimeQuantum * ms_to_abs_time; 220 time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time; 221 time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time; 222 time_constraints.preemptible = 0; 223 return time_constraints; 224} 225 226// Enables time-constraint policy and priority suitable for low-latency, 227// glitch-resistant audio. 228void SetPriorityRealtimeAudio(TimeDelta realtime_period) { 229 // Increase thread priority to real-time. 230 231 // Please note that the thread_policy_set() calls may fail in 232 // rare cases if the kernel decides the system is under heavy load 233 // and is unable to handle boosting the thread priority. 234 // In these cases we just return early and go on with life. 235 236 mach_port_t mach_thread_id = 237 pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle()); 238 239 // Make thread fixed priority. 240 thread_extended_policy_data_t policy; 241 policy.timeshare = 0; // Set to 1 for a non-fixed thread. 242 kern_return_t result = thread_policy_set( 243 mach_thread_id, THREAD_EXTENDED_POLICY, 244 reinterpret_cast<thread_policy_t>(&policy), THREAD_EXTENDED_POLICY_COUNT); 245 if (result != KERN_SUCCESS) { 246 MACH_DVLOG(1, result) << "thread_policy_set"; 247 return; 248 } 249 250 // Set to relatively high priority. 251 thread_precedence_policy_data_t precedence; 252 precedence.importance = 63; 253 result = thread_policy_set(mach_thread_id, THREAD_PRECEDENCE_POLICY, 254 reinterpret_cast<thread_policy_t>(&precedence), 255 THREAD_PRECEDENCE_POLICY_COUNT); 256 if (result != KERN_SUCCESS) { 257 MACH_DVLOG(1, result) << "thread_policy_set"; 258 return; 259 } 260 261 // Most important, set real-time constraints. 262 263 thread_time_constraint_policy_data_t time_constraints = 264 GetTimeConstraints(realtime_period); 265 266 result = 267 thread_policy_set(mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY, 268 reinterpret_cast<thread_policy_t>(&time_constraints), 269 THREAD_TIME_CONSTRAINT_POLICY_COUNT); 270 MACH_DVLOG_IF(1, result != KERN_SUCCESS, result) << "thread_policy_set"; 271 return; 272} 273 274} // anonymous namespace 275 276// static 277TimeDelta PlatformThreadApple::GetCurrentThreadRealtimePeriodForTest() { 278 return GetCurrentThreadRealtimePeriod(); 279} 280 281// static 282bool PlatformThreadBase::CanChangeThreadType(ThreadType from, ThreadType to) { 283 return true; 284} 285 286namespace internal { 287 288void SetCurrentThreadTypeImpl(ThreadType thread_type, 289 MessagePumpType pump_type_hint) { 290 switch (thread_type) { 291 case ThreadType::kBackground: 292 pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0); 293 break; 294 case ThreadType::kUtility: 295 pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0); 296 break; 297 case ThreadType::kResourceEfficient: 298 pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0); 299 break; 300 case ThreadType::kDefault: 301 pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); 302 break; 303 case ThreadType::kDisplayCritical: { 304 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); 305 break; 306 } 307 case ThreadType::kRealtimeAudio: 308 SetPriorityRealtimeAudio(GetCurrentThreadRealtimePeriod()); 309 DCHECK_EQ([NSThread.currentThread threadPriority], 1.0); 310 break; 311 } 312} 313 314} // namespace internal 315 316// static 317ThreadPriorityForTest PlatformThreadBase::GetCurrentThreadPriorityForTest() { 318 if ([NSThread.currentThread threadPriority] == 1.0) { 319 // Set to 1 for a non-fixed thread.) 320 return ThreadPriorityForTest::kRealtimeAudio; 321 } 322 323 qos_class_t qos_class; 324 int relative_priority; 325 pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority); 326 switch (qos_class) { 327 case QOS_CLASS_BACKGROUND: 328 return ThreadPriorityForTest::kBackground; 329 case QOS_CLASS_UTILITY: 330 return ThreadPriorityForTest::kUtility; 331 case QOS_CLASS_USER_INITIATED: 332 return ThreadPriorityForTest::kNormal; 333 case QOS_CLASS_USER_INTERACTIVE: 334 return ThreadPriorityForTest::kDisplay; 335 default: 336 return ThreadPriorityForTest::kNormal; 337 } 338} 339 340size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) { 341#if BUILDFLAG(IS_IOS) 342#if BUILDFLAG(USE_BLINK) 343 // For iOS 512kB (the default) isn't sufficient, but using the code 344 // for macOS below will return 8MB. So just be a little more conservative 345 // and return 1MB for now. 346 return 1024 * 1024; 347#else 348 return 0; 349#endif 350#else 351 // The macOS default for a pthread stack size is 512kB. 352 // Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses 353 // DEFAULT_STACK_SIZE for this purpose. 354 // 355 // 512kB isn't quite generous enough for some deeply recursive threads that 356 // otherwise request the default stack size by specifying 0. Here, adopt 357 // glibc's behavior as on Linux, which is to use the current stack size 358 // limit (ulimit -s) as the default stack size. See 359 // glibc-2.11.1/nptl/nptl-init.c's __pthread_initialize_minimal_internal. To 360 // avoid setting the limit below the macOS default or the minimum usable 361 // stack size, these values are also considered. If any of these values 362 // can't be determined, or if stack size is unlimited (ulimit -s unlimited), 363 // stack_size is left at 0 to get the system default. 364 // 365 // macOS normally only applies ulimit -s to the main thread stack. On 366 // contemporary macOS and Linux systems alike, this value is generally 8MB 367 // or in that neighborhood. 368 size_t default_stack_size = 0; 369 struct rlimit stack_rlimit; 370 if (pthread_attr_getstacksize(&attributes, &default_stack_size) == 0 && 371 getrlimit(RLIMIT_STACK, &stack_rlimit) == 0 && 372 stack_rlimit.rlim_cur != RLIM_INFINITY) { 373 default_stack_size = std::max( 374 std::max(default_stack_size, static_cast<size_t>(PTHREAD_STACK_MIN)), 375 static_cast<size_t>(stack_rlimit.rlim_cur)); 376 } 377 return default_stack_size; 378#endif 379} 380 381void TerminateOnThread() {} 382 383} // namespace base 384