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