• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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