• 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 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