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 96const Feature kUserInteractiveCompositingMac{"UserInteractiveCompositingMac", 97 FEATURE_ENABLED_BY_DEFAULT}; 98 99namespace { 100 101bool IsOptimizedRealtimeThreadingMacEnabled() { 102 return FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac); 103} 104 105} // namespace 106 107// Fine-tuning optimized real-time thread config: 108// Whether or not the thread should be preemptible. 109const FeatureParam<bool> kOptimizedRealtimeThreadingMacPreemptible{ 110 &kOptimizedRealtimeThreadingMac, "preemptible", true}; 111// Portion of the time quantum the thread is expected to be busy, (0, 1]. 112const FeatureParam<double> kOptimizedRealtimeThreadingMacBusy{ 113 &kOptimizedRealtimeThreadingMac, "busy", 0.5}; 114// Maximum portion of the time quantum the thread is expected to be busy, 115// (kOptimizedRealtimeThreadingMacBusy, 1]. 116const FeatureParam<double> kOptimizedRealtimeThreadingMacBusyLimit{ 117 &kOptimizedRealtimeThreadingMac, "busy_limit", 1.0}; 118std::atomic<bool> g_user_interactive_compositing( 119 kUserInteractiveCompositingMac.default_state == FEATURE_ENABLED_BY_DEFAULT); 120 121namespace { 122 123struct TimeConstraints { 124 bool preemptible{kOptimizedRealtimeThreadingMacPreemptible.default_value}; 125 double busy{kOptimizedRealtimeThreadingMacBusy.default_value}; 126 double busy_limit{kOptimizedRealtimeThreadingMacBusyLimit.default_value}; 127 128 static TimeConstraints ReadFromFeatureParams() { 129 double busy_limit = kOptimizedRealtimeThreadingMacBusyLimit.Get(); 130 return TimeConstraints{ 131 kOptimizedRealtimeThreadingMacPreemptible.Get(), 132 std::min(busy_limit, kOptimizedRealtimeThreadingMacBusy.Get()), 133 busy_limit}; 134 } 135}; 136 137// Use atomics to access FeatureList values when setting up a thread, since 138// there are cases when FeatureList initialization is not synchronized with 139// PlatformThread creation. 140std::atomic<bool> g_use_optimized_realtime_threading( 141 kOptimizedRealtimeThreadingMac.default_state == FEATURE_ENABLED_BY_DEFAULT); 142std::atomic<TimeConstraints> g_time_constraints; 143 144} // namespace 145 146// static 147void PlatformThreadApple::InitializeFeatures() { 148 g_time_constraints.store(TimeConstraints::ReadFromFeatureParams()); 149 g_use_optimized_realtime_threading.store( 150 IsOptimizedRealtimeThreadingMacEnabled()); 151 g_user_interactive_compositing.store( 152 FeatureList::IsEnabled(kUserInteractiveCompositingMac)); 153} 154 155// static 156void PlatformThreadApple::SetCurrentThreadRealtimePeriodValue( 157 TimeDelta realtime_period) { 158 if (g_use_optimized_realtime_threading.load()) { 159 NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey] = 160 @(realtime_period.InNanoseconds()); 161 } 162} 163 164namespace { 165 166TimeDelta GetCurrentThreadRealtimePeriod() { 167 NSNumber* period = apple::ObjCCast<NSNumber>( 168 NSThread.currentThread.threadDictionary[kRealtimePeriodNsKey]); 169 170 return period ? Nanoseconds(period.longLongValue) : TimeDelta(); 171} 172 173// Calculates time constraints for THREAD_TIME_CONSTRAINT_POLICY. 174// |realtime_period| is used as a base if it's non-zero. 175// Otherwise we fall back to empirical values. 176thread_time_constraint_policy_data_t GetTimeConstraints( 177 TimeDelta realtime_period) { 178 thread_time_constraint_policy_data_t time_constraints; 179 mach_timebase_info_data_t tb_info; 180 mach_timebase_info(&tb_info); 181 182 if (!realtime_period.is_zero()) { 183 // Limit the lowest value to 2.9 ms we used to have historically. The lower 184 // the period, the more CPU frequency may go up, and we don't want to risk 185 // worsening the thermal situation. 186 uint32_t abs_realtime_period = saturated_cast<uint32_t>( 187 std::max(realtime_period.InNanoseconds(), 2900000LL) * 188 (double(tb_info.denom) / tb_info.numer)); 189 TimeConstraints config = g_time_constraints.load(); 190 time_constraints.period = abs_realtime_period; 191 time_constraints.constraint = std::min( 192 abs_realtime_period, uint32_t(abs_realtime_period * config.busy_limit)); 193 time_constraints.computation = 194 std::min(time_constraints.constraint, 195 uint32_t(abs_realtime_period * config.busy)); 196 time_constraints.preemptible = config.preemptible ? YES : NO; 197 return time_constraints; 198 } 199 200 // Empirical configuration. 201 202 // Define the guaranteed and max fraction of time for the audio thread. 203 // These "duty cycle" values can range from 0 to 1. A value of 0.5 204 // means the scheduler would give half the time to the thread. 205 // These values have empirically been found to yield good behavior. 206 // Good means that audio performance is high and other threads won't starve. 207 const double kGuaranteedAudioDutyCycle = 0.75; 208 const double kMaxAudioDutyCycle = 0.85; 209 210 // Define constants determining how much time the audio thread can 211 // use in a given time quantum. All times are in milliseconds. 212 213 // About 128 frames @44.1KHz 214 const double kTimeQuantum = 2.9; 215 216 // Time guaranteed each quantum. 217 const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; 218 219 // Maximum time each quantum. 220 const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; 221 222 // Get the conversion factor from milliseconds to absolute time 223 // which is what the time-constraints call needs. 224 double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000; 225 226 time_constraints.period = kTimeQuantum * ms_to_abs_time; 227 time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time; 228 time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time; 229 time_constraints.preemptible = 0; 230 return time_constraints; 231} 232 233// Enables time-constraint policy and priority suitable for low-latency, 234// glitch-resistant audio. 235void SetPriorityRealtimeAudio(TimeDelta realtime_period) { 236 // Increase thread priority to real-time. 237 238 // Please note that the thread_policy_set() calls may fail in 239 // rare cases if the kernel decides the system is under heavy load 240 // and is unable to handle boosting the thread priority. 241 // In these cases we just return early and go on with life. 242 243 mach_port_t mach_thread_id = 244 pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle()); 245 246 // Make thread fixed priority. 247 thread_extended_policy_data_t policy; 248 policy.timeshare = 0; // Set to 1 for a non-fixed thread. 249 kern_return_t result = thread_policy_set( 250 mach_thread_id, THREAD_EXTENDED_POLICY, 251 reinterpret_cast<thread_policy_t>(&policy), THREAD_EXTENDED_POLICY_COUNT); 252 if (result != KERN_SUCCESS) { 253 MACH_DVLOG(1, result) << "thread_policy_set"; 254 return; 255 } 256 257 // Set to relatively high priority. 258 thread_precedence_policy_data_t precedence; 259 precedence.importance = 63; 260 result = thread_policy_set(mach_thread_id, THREAD_PRECEDENCE_POLICY, 261 reinterpret_cast<thread_policy_t>(&precedence), 262 THREAD_PRECEDENCE_POLICY_COUNT); 263 if (result != KERN_SUCCESS) { 264 MACH_DVLOG(1, result) << "thread_policy_set"; 265 return; 266 } 267 268 // Most important, set real-time constraints. 269 270 thread_time_constraint_policy_data_t time_constraints = 271 GetTimeConstraints(realtime_period); 272 273 result = 274 thread_policy_set(mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY, 275 reinterpret_cast<thread_policy_t>(&time_constraints), 276 THREAD_TIME_CONSTRAINT_POLICY_COUNT); 277 MACH_DVLOG_IF(1, result != KERN_SUCCESS, result) << "thread_policy_set"; 278 return; 279} 280 281} // anonymous namespace 282 283// static 284TimeDelta PlatformThreadApple::GetCurrentThreadRealtimePeriodForTest() { 285 return GetCurrentThreadRealtimePeriod(); 286} 287 288// static 289bool PlatformThreadBase::CanChangeThreadType(ThreadType from, ThreadType to) { 290 return true; 291} 292 293namespace internal { 294 295void SetCurrentThreadTypeImpl(ThreadType thread_type, 296 MessagePumpType pump_type_hint) { 297 // Changing the priority of the main thread causes performance 298 // regressions. https://crbug.com/601270 299 // TODO(https://crbug.com/1280764): Remove this check. kCompositing is the 300 // default on Mac, so this check is counter intuitive. 301 if ([[NSThread currentThread] isMainThread] && 302 thread_type >= ThreadType::kCompositing && 303 !g_user_interactive_compositing.load(std::memory_order_relaxed)) { 304 DCHECK(thread_type == ThreadType::kDefault || 305 thread_type == ThreadType::kCompositing); 306 return; 307 } 308 309 switch (thread_type) { 310 case ThreadType::kBackground: 311 pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0); 312 break; 313 case ThreadType::kUtility: 314 pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0); 315 break; 316 case ThreadType::kResourceEfficient: 317 pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0); 318 break; 319 case ThreadType::kDefault: 320 pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); 321 break; 322 case ThreadType::kCompositing: 323 if (g_user_interactive_compositing.load(std::memory_order_relaxed)) { 324 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); 325 } else { 326 pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); 327 } 328 break; 329 case ThreadType::kDisplayCritical: { 330 pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); 331 break; 332 } 333 case ThreadType::kRealtimeAudio: 334 SetPriorityRealtimeAudio(GetCurrentThreadRealtimePeriod()); 335 DCHECK_EQ([NSThread.currentThread threadPriority], 1.0); 336 break; 337 } 338} 339 340} // namespace internal 341 342// static 343ThreadPriorityForTest PlatformThreadBase::GetCurrentThreadPriorityForTest() { 344 if ([NSThread.currentThread threadPriority] == 1.0) { 345 // Set to 1 for a non-fixed thread.) 346 return ThreadPriorityForTest::kRealtimeAudio; 347 } 348 349 qos_class_t qos_class; 350 int relative_priority; 351 pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority); 352 switch (qos_class) { 353 case QOS_CLASS_BACKGROUND: 354 return ThreadPriorityForTest::kBackground; 355 case QOS_CLASS_UTILITY: 356 return ThreadPriorityForTest::kUtility; 357 case QOS_CLASS_USER_INITIATED: 358 return ThreadPriorityForTest::kNormal; 359 case QOS_CLASS_USER_INTERACTIVE: 360 return ThreadPriorityForTest::kDisplay; 361 default: 362 return ThreadPriorityForTest::kNormal; 363 } 364} 365 366size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) { 367#if BUILDFLAG(IS_IOS) 368#if BUILDFLAG(USE_BLINK) 369 // For iOS 512kB (the default) isn't sufficient, but using the code 370 // for macOS below will return 8MB. So just be a little more conservative 371 // and return 1MB for now. 372 return 1024 * 1024; 373#else 374 return 0; 375#endif 376#else 377 // The macOS default for a pthread stack size is 512kB. 378 // Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses 379 // DEFAULT_STACK_SIZE for this purpose. 380 // 381 // 512kB isn't quite generous enough for some deeply recursive threads that 382 // otherwise request the default stack size by specifying 0. Here, adopt 383 // glibc's behavior as on Linux, which is to use the current stack size 384 // limit (ulimit -s) as the default stack size. See 385 // glibc-2.11.1/nptl/nptl-init.c's __pthread_initialize_minimal_internal. To 386 // avoid setting the limit below the macOS default or the minimum usable 387 // stack size, these values are also considered. If any of these values 388 // can't be determined, or if stack size is unlimited (ulimit -s unlimited), 389 // stack_size is left at 0 to get the system default. 390 // 391 // macOS normally only applies ulimit -s to the main thread stack. On 392 // contemporary macOS and Linux systems alike, this value is generally 8MB 393 // or in that neighborhood. 394 size_t default_stack_size = 0; 395 struct rlimit stack_rlimit; 396 if (pthread_attr_getstacksize(&attributes, &default_stack_size) == 0 && 397 getrlimit(RLIMIT_STACK, &stack_rlimit) == 0 && 398 stack_rlimit.rlim_cur != RLIM_INFINITY) { 399 default_stack_size = std::max( 400 std::max(default_stack_size, static_cast<size_t>(PTHREAD_STACK_MIN)), 401 static_cast<size_t>(stack_rlimit.rlim_cur)); 402 } 403 return default_stack_size; 404#endif 405} 406 407void TerminateOnThread() {} 408 409} // namespace base 410