xref: /aosp_15_r20/external/cronet/base/threading/platform_thread_apple.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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