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_win.h"
6
7 #include <stddef.h>
8
9 #include <string>
10
11 #include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
12 #include "base/debug/alias.h"
13 #include "base/debug/crash_logging.h"
14 #include "base/debug/profiler.h"
15 #include "base/feature_list.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/process/memory.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/threading/scoped_blocking_call.h"
23 #include "base/threading/scoped_thread_priority.h"
24 #include "base/threading/thread_id_name_manager.h"
25 #include "base/threading/thread_restrictions.h"
26 #include "base/threading/threading_features.h"
27 #include "base/time/time_override.h"
28 #include "base/win/scoped_handle.h"
29 #include "base/win/windows_version.h"
30 #include "build/build_config.h"
31
32 #include <windows.h>
33
34 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
35 #include "base/allocator/partition_allocator/starscan/pcscan.h"
36 #include "base/allocator/partition_allocator/starscan/stack/stack.h"
37 #endif
38
39 namespace base {
40
41 BASE_FEATURE(kUseThreadPriorityLowest,
42 "UseThreadPriorityLowest",
43 base::FEATURE_DISABLED_BY_DEFAULT);
44 BASE_FEATURE(kAboveNormalCompositingBrowserWin,
45 "AboveNormalCompositingBrowserWin",
46 base::FEATURE_DISABLED_BY_DEFAULT);
47
48 namespace {
49
50 // Flag used to set thread priority to |THREAD_PRIORITY_LOWEST| for
51 // |kUseThreadPriorityLowest| Feature.
52 std::atomic<bool> g_use_thread_priority_lowest{false};
53 // Flag used to map Compositing ThreadType |THREAD_PRIORITY_ABOVE_NORMAL| on the
54 // UI thread for |kAboveNormalCompositingBrowserWin| Feature.
55 std::atomic<bool> g_above_normal_compositing_browser{false};
56
57 // These values are sometimes returned by ::GetThreadPriority().
58 constexpr int kWinDisplayPriority1 = 5;
59 constexpr int kWinDisplayPriority2 = 6;
60
61 // The information on how to set the thread name comes from
62 // a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx
63 const DWORD kVCThreadNameException = 0x406D1388;
64
65 typedef struct tagTHREADNAME_INFO {
66 DWORD dwType; // Must be 0x1000.
67 LPCSTR szName; // Pointer to name (in user addr space).
68 DWORD dwThreadID; // Thread ID (-1=caller thread).
69 DWORD dwFlags; // Reserved for future use, must be zero.
70 } THREADNAME_INFO;
71
72 // The SetThreadDescription API was brought in version 1607 of Windows 10.
73 typedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread,
74 PCWSTR lpThreadDescription);
75
76 // This function has try handling, so it is separated out of its caller.
SetNameInternal(PlatformThreadId thread_id,const char * name)77 void SetNameInternal(PlatformThreadId thread_id, const char* name) {
78 THREADNAME_INFO info;
79 info.dwType = 0x1000;
80 info.szName = name;
81 info.dwThreadID = thread_id;
82 info.dwFlags = 0;
83
84 __try {
85 RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(ULONG_PTR),
86 reinterpret_cast<ULONG_PTR*>(&info));
87 } __except (EXCEPTION_EXECUTE_HANDLER) {
88 }
89 }
90
91 struct ThreadParams {
92 raw_ptr<PlatformThread::Delegate> delegate;
93 bool joinable;
94 ThreadType thread_type;
95 MessagePumpType message_pump_type;
96 };
97
ThreadFunc(void * params)98 DWORD __stdcall ThreadFunc(void* params) {
99 ThreadParams* thread_params = static_cast<ThreadParams*>(params);
100 PlatformThread::Delegate* delegate = thread_params->delegate;
101 if (!thread_params->joinable)
102 base::DisallowSingleton();
103
104 if (thread_params->thread_type != ThreadType::kDefault)
105 internal::SetCurrentThreadType(thread_params->thread_type,
106 thread_params->message_pump_type);
107
108 // Retrieve a copy of the thread handle to use as the key in the
109 // thread name mapping.
110 PlatformThreadHandle::Handle platform_handle;
111 BOOL did_dup = DuplicateHandle(GetCurrentProcess(),
112 GetCurrentThread(),
113 GetCurrentProcess(),
114 &platform_handle,
115 0,
116 FALSE,
117 DUPLICATE_SAME_ACCESS);
118
119 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
120 partition_alloc::internal::PCScan::NotifyThreadCreated(
121 partition_alloc::internal::GetStackPointer());
122 #endif
123
124 win::ScopedHandle scoped_platform_handle;
125
126 if (did_dup) {
127 scoped_platform_handle.Set(platform_handle);
128 ThreadIdNameManager::GetInstance()->RegisterThread(
129 scoped_platform_handle.get(), PlatformThread::CurrentId());
130 }
131
132 delete thread_params;
133 delegate->ThreadMain();
134
135 if (did_dup) {
136 ThreadIdNameManager::GetInstance()->RemoveName(scoped_platform_handle.get(),
137 PlatformThread::CurrentId());
138 }
139
140 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
141 partition_alloc::internal::PCScan::NotifyThreadDestroyed();
142 #endif
143
144 // Ensure thread priority is at least NORMAL before initiating thread
145 // destruction. Thread destruction on Windows holds the LdrLock while
146 // performing TLS destruction which causes hangs if performed at background
147 // priority (priority inversion) (see: http://crbug.com/1096203).
148 if (::GetThreadPriority(::GetCurrentThread()) < THREAD_PRIORITY_NORMAL)
149 PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
150
151 return 0;
152 }
153
154 // CreateThreadInternal() matches PlatformThread::CreateWithType(), except
155 // that |out_thread_handle| may be nullptr, in which case a non-joinable thread
156 // is created.
CreateThreadInternal(size_t stack_size,PlatformThread::Delegate * delegate,PlatformThreadHandle * out_thread_handle,ThreadType thread_type,MessagePumpType message_pump_type)157 bool CreateThreadInternal(size_t stack_size,
158 PlatformThread::Delegate* delegate,
159 PlatformThreadHandle* out_thread_handle,
160 ThreadType thread_type,
161 MessagePumpType message_pump_type) {
162 unsigned int flags = 0;
163 if (stack_size > 0) {
164 flags = STACK_SIZE_PARAM_IS_A_RESERVATION;
165 #if defined(ARCH_CPU_32_BITS)
166 } else {
167 // The process stack size is increased to give spaces to |RendererMain| in
168 // |chrome/BUILD.gn|, but keep the default stack size of other threads to
169 // 1MB for the address space pressure.
170 flags = STACK_SIZE_PARAM_IS_A_RESERVATION;
171 static BOOL is_wow64 = -1;
172 if (is_wow64 == -1 && !IsWow64Process(GetCurrentProcess(), &is_wow64))
173 is_wow64 = FALSE;
174 // When is_wow64 is set that means we are running on 64-bit Windows and we
175 // get 4 GiB of address space. In that situation we can afford to use 1 MiB
176 // of address space for stacks. When running on 32-bit Windows we only get
177 // 2 GiB of address space so we need to conserve. Typically stack usage on
178 // these threads is only about 100 KiB.
179 if (is_wow64)
180 stack_size = 1024 * 1024;
181 else
182 stack_size = 512 * 1024;
183 #endif
184 }
185
186 ThreadParams* params = new ThreadParams;
187 params->delegate = delegate;
188 params->joinable = out_thread_handle != nullptr;
189 params->thread_type = thread_type;
190 params->message_pump_type = message_pump_type;
191
192 // Using CreateThread here vs _beginthreadex makes thread creation a bit
193 // faster and doesn't require the loader lock to be available. Our code will
194 // have to work running on CreateThread() threads anyway, since we run code on
195 // the Windows thread pool, etc. For some background on the difference:
196 // http://www.microsoft.com/msj/1099/win32/win321099.aspx
197 void* thread_handle =
198 ::CreateThread(nullptr, stack_size, ThreadFunc, params, flags, nullptr);
199
200 if (!thread_handle) {
201 DWORD last_error = ::GetLastError();
202
203 switch (last_error) {
204 case ERROR_NOT_ENOUGH_MEMORY:
205 case ERROR_OUTOFMEMORY:
206 case ERROR_COMMITMENT_LIMIT:
207 TerminateBecauseOutOfMemory(stack_size);
208 break;
209
210 default:
211 static auto* last_error_crash_key = debug::AllocateCrashKeyString(
212 "create_thread_last_error", debug::CrashKeySize::Size32);
213 debug::SetCrashKeyString(last_error_crash_key,
214 base::NumberToString(last_error));
215 break;
216 }
217
218 delete params;
219 return false;
220 }
221
222 if (out_thread_handle)
223 *out_thread_handle = PlatformThreadHandle(thread_handle);
224 else
225 CloseHandle(thread_handle);
226 return true;
227 }
228
229 } // namespace
230
231 namespace internal {
232
AssertMemoryPriority(HANDLE thread,int memory_priority)233 void AssertMemoryPriority(HANDLE thread, int memory_priority) {
234 #if DCHECK_IS_ON()
235 static const auto get_thread_information_fn =
236 reinterpret_cast<decltype(&::GetThreadInformation)>(::GetProcAddress(
237 ::GetModuleHandle(L"Kernel32.dll"), "GetThreadInformation"));
238
239 DCHECK(get_thread_information_fn);
240
241 MEMORY_PRIORITY_INFORMATION memory_priority_information = {};
242 DCHECK(get_thread_information_fn(thread, ::ThreadMemoryPriority,
243 &memory_priority_information,
244 sizeof(memory_priority_information)));
245
246 DCHECK_EQ(memory_priority,
247 static_cast<int>(memory_priority_information.MemoryPriority));
248 #endif
249 }
250
251 } // namespace internal
252
253 // static
CurrentId()254 PlatformThreadId PlatformThread::CurrentId() {
255 return ::GetCurrentThreadId();
256 }
257
258 // static
CurrentRef()259 PlatformThreadRef PlatformThread::CurrentRef() {
260 return PlatformThreadRef(::GetCurrentThreadId());
261 }
262
263 // static
CurrentHandle()264 PlatformThreadHandle PlatformThread::CurrentHandle() {
265 return PlatformThreadHandle(::GetCurrentThread());
266 }
267
268 // static
YieldCurrentThread()269 void PlatformThread::YieldCurrentThread() {
270 ::Sleep(0);
271 }
272
273 // static
Sleep(TimeDelta duration)274 void PlatformThread::Sleep(TimeDelta duration) {
275 // When measured with a high resolution clock, Sleep() sometimes returns much
276 // too early. We may need to call it repeatedly to get the desired duration.
277 // PlatformThread::Sleep doesn't support mock-time, so this always uses
278 // real-time.
279 const TimeTicks end = subtle::TimeTicksNowIgnoringOverride() + duration;
280 for (TimeTicks now = subtle::TimeTicksNowIgnoringOverride(); now < end;
281 now = subtle::TimeTicksNowIgnoringOverride()) {
282 ::Sleep(static_cast<DWORD>((end - now).InMillisecondsRoundedUp()));
283 }
284 }
285
286 // static
SetName(const std::string & name)287 void PlatformThread::SetName(const std::string& name) {
288 ThreadIdNameManager::GetInstance()->SetName(name);
289
290 // The SetThreadDescription API works even if no debugger is attached.
291 static auto set_thread_description_func =
292 reinterpret_cast<SetThreadDescription>(::GetProcAddress(
293 ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
294 if (set_thread_description_func) {
295 set_thread_description_func(::GetCurrentThread(),
296 base::UTF8ToWide(name).c_str());
297 }
298
299 // The debugger needs to be around to catch the name in the exception. If
300 // there isn't a debugger, we are just needlessly throwing an exception.
301 if (!::IsDebuggerPresent())
302 return;
303
304 SetNameInternal(CurrentId(), name.c_str());
305 }
306
307 // static
GetName()308 const char* PlatformThread::GetName() {
309 return ThreadIdNameManager::GetInstance()->GetName(CurrentId());
310 }
311
312 // static
CreateWithType(size_t stack_size,Delegate * delegate,PlatformThreadHandle * thread_handle,ThreadType thread_type,MessagePumpType pump_type_hint)313 bool PlatformThread::CreateWithType(size_t stack_size,
314 Delegate* delegate,
315 PlatformThreadHandle* thread_handle,
316 ThreadType thread_type,
317 MessagePumpType pump_type_hint) {
318 DCHECK(thread_handle);
319 return CreateThreadInternal(stack_size, delegate, thread_handle, thread_type,
320 pump_type_hint);
321 }
322
323 // static
CreateNonJoinable(size_t stack_size,Delegate * delegate)324 bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) {
325 return CreateNonJoinableWithType(stack_size, delegate, ThreadType::kDefault);
326 }
327
328 // static
CreateNonJoinableWithType(size_t stack_size,Delegate * delegate,ThreadType thread_type,MessagePumpType pump_type_hint)329 bool PlatformThread::CreateNonJoinableWithType(size_t stack_size,
330 Delegate* delegate,
331 ThreadType thread_type,
332 MessagePumpType pump_type_hint) {
333 return CreateThreadInternal(stack_size, delegate, nullptr /* non-joinable */,
334 thread_type, pump_type_hint);
335 }
336
337 // static
Join(PlatformThreadHandle thread_handle)338 void PlatformThread::Join(PlatformThreadHandle thread_handle) {
339 DCHECK(thread_handle.platform_handle());
340
341 DWORD thread_id = 0;
342 thread_id = ::GetThreadId(thread_handle.platform_handle());
343 DWORD last_error = 0;
344 if (!thread_id)
345 last_error = ::GetLastError();
346
347 // Record information about the exiting thread in case joining hangs.
348 base::debug::Alias(&thread_id);
349 base::debug::Alias(&last_error);
350
351 base::internal::ScopedBlockingCallWithBaseSyncPrimitives scoped_blocking_call(
352 FROM_HERE, base::BlockingType::MAY_BLOCK);
353
354 // Wait for the thread to exit. It should already have terminated but make
355 // sure this assumption is valid.
356 CHECK_EQ(WAIT_OBJECT_0,
357 WaitForSingleObject(thread_handle.platform_handle(), INFINITE));
358 CloseHandle(thread_handle.platform_handle());
359 }
360
361 // static
Detach(PlatformThreadHandle thread_handle)362 void PlatformThread::Detach(PlatformThreadHandle thread_handle) {
363 CloseHandle(thread_handle.platform_handle());
364 }
365
366 // static
CanChangeThreadType(ThreadType from,ThreadType to)367 bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
368 return true;
369 }
370
371 namespace {
372
SetCurrentThreadPriority(ThreadType thread_type,MessagePumpType pump_type_hint)373 void SetCurrentThreadPriority(ThreadType thread_type,
374 MessagePumpType pump_type_hint) {
375 if (thread_type == ThreadType::kCompositing &&
376 pump_type_hint == MessagePumpType::UI &&
377 !g_above_normal_compositing_browser) {
378 // Ignore kCompositing thread type for UI thread as Windows has a
379 // priority boost mechanism. See
380 // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts
381 return;
382 }
383
384 PlatformThreadHandle::Handle thread_handle =
385 PlatformThread::CurrentHandle().platform_handle();
386
387 if (!g_use_thread_priority_lowest && thread_type != ThreadType::kBackground) {
388 // Exit background mode if the new priority is not BACKGROUND. This is a
389 // no-op if not in background mode.
390 ::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END);
391 // We used to DCHECK that memory priority is MEMORY_PRIORITY_NORMAL here,
392 // but found that it is not always the case (e.g. in the installer).
393 // crbug.com/1340578#c2
394 }
395
396 int desired_priority = THREAD_PRIORITY_ERROR_RETURN;
397 switch (thread_type) {
398 case ThreadType::kBackground:
399 // Using THREAD_MODE_BACKGROUND_BEGIN instead of THREAD_PRIORITY_LOWEST
400 // improves input latency and navigation time. See
401 // https://docs.google.com/document/d/16XrOwuwTwKWdgPbcKKajTmNqtB4Am8TgS9GjbzBYLc0
402 //
403 // MSDN recommends THREAD_MODE_BACKGROUND_BEGIN for threads that perform
404 // background work, as it reduces disk and memory priority in addition to
405 // CPU priority.
406 desired_priority =
407 g_use_thread_priority_lowest.load(std::memory_order_relaxed)
408 ? THREAD_PRIORITY_LOWEST
409 : THREAD_MODE_BACKGROUND_BEGIN;
410 break;
411 case ThreadType::kUtility:
412 desired_priority = THREAD_PRIORITY_BELOW_NORMAL;
413 break;
414 case ThreadType::kResourceEfficient:
415 case ThreadType::kDefault:
416 desired_priority = THREAD_PRIORITY_NORMAL;
417 break;
418 case ThreadType::kCompositing:
419 case ThreadType::kDisplayCritical:
420 desired_priority = THREAD_PRIORITY_ABOVE_NORMAL;
421 break;
422 case ThreadType::kRealtimeAudio:
423 desired_priority = THREAD_PRIORITY_TIME_CRITICAL;
424 break;
425 }
426 DCHECK_NE(desired_priority, THREAD_PRIORITY_ERROR_RETURN);
427
428 [[maybe_unused]] const BOOL success =
429 ::SetThreadPriority(thread_handle, desired_priority);
430 DPLOG_IF(ERROR, !success)
431 << "Failed to set thread priority to " << desired_priority;
432
433 if (!g_use_thread_priority_lowest && thread_type == ThreadType::kBackground) {
434 // In a background process, THREAD_MODE_BACKGROUND_BEGIN lowers the memory
435 // and I/O priorities but not the CPU priority (kernel bug?). Use
436 // THREAD_PRIORITY_LOWEST to also lower the CPU priority.
437 // https://crbug.com/901483
438 if (PlatformThread::GetCurrentThreadPriorityForTest() !=
439 ThreadPriorityForTest::kBackground) {
440 ::SetThreadPriority(thread_handle, THREAD_PRIORITY_LOWEST);
441 // We used to DCHECK that memory priority is MEMORY_PRIORITY_VERY_LOW
442 // here, but found that it is not always the case (e.g. in the installer).
443 // crbug.com/1340578#c2
444 }
445 }
446 }
447
SetCurrentThreadQualityOfService(ThreadType thread_type)448 void SetCurrentThreadQualityOfService(ThreadType thread_type) {
449 // QoS and power throttling were introduced in Win10 1709
450 if (win::GetVersion() < win::Version::WIN10_RS3) {
451 return;
452 }
453
454 static const auto set_thread_information_fn =
455 reinterpret_cast<decltype(&::SetThreadInformation)>(::GetProcAddress(
456 ::GetModuleHandle(L"kernel32.dll"), "SetThreadInformation"));
457 DCHECK(set_thread_information_fn);
458
459 bool desire_ecoqos = false;
460 switch (thread_type) {
461 case ThreadType::kBackground:
462 case ThreadType::kUtility:
463 case ThreadType::kResourceEfficient:
464 desire_ecoqos = true;
465 break;
466 case ThreadType::kDefault:
467 case ThreadType::kCompositing:
468 case ThreadType::kDisplayCritical:
469 case ThreadType::kRealtimeAudio:
470 desire_ecoqos = false;
471 break;
472 }
473
474 THREAD_POWER_THROTTLING_STATE thread_power_throttling_state{
475 .Version = THREAD_POWER_THROTTLING_CURRENT_VERSION,
476 .ControlMask =
477 desire_ecoqos ? THREAD_POWER_THROTTLING_EXECUTION_SPEED : 0ul,
478 .StateMask =
479 desire_ecoqos ? THREAD_POWER_THROTTLING_EXECUTION_SPEED : 0ul,
480 };
481 [[maybe_unused]] const BOOL success = set_thread_information_fn(
482 ::GetCurrentThread(), ::ThreadPowerThrottling,
483 &thread_power_throttling_state, sizeof(thread_power_throttling_state));
484 DPLOG_IF(ERROR, !success)
485 << "Failed to set EcoQoS to " << std::boolalpha << desire_ecoqos;
486 }
487
488 } // namespace
489
490 namespace internal {
491
SetCurrentThreadTypeImpl(ThreadType thread_type,MessagePumpType pump_type_hint)492 void SetCurrentThreadTypeImpl(ThreadType thread_type,
493 MessagePumpType pump_type_hint) {
494 SetCurrentThreadPriority(thread_type, pump_type_hint);
495 SetCurrentThreadQualityOfService(thread_type);
496 }
497
498 } // namespace internal
499
500 // static
GetCurrentThreadPriorityForTest()501 ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
502 static_assert(
503 THREAD_PRIORITY_IDLE < 0,
504 "THREAD_PRIORITY_IDLE is >= 0 and will incorrectly cause errors.");
505 static_assert(
506 THREAD_PRIORITY_LOWEST < 0,
507 "THREAD_PRIORITY_LOWEST is >= 0 and will incorrectly cause errors.");
508 static_assert(THREAD_PRIORITY_BELOW_NORMAL < 0,
509 "THREAD_PRIORITY_BELOW_NORMAL is >= 0 and will incorrectly "
510 "cause errors.");
511 static_assert(
512 THREAD_PRIORITY_NORMAL == 0,
513 "The logic below assumes that THREAD_PRIORITY_NORMAL is zero. If it is "
514 "not, ThreadPriorityForTest::kBackground may be incorrectly detected.");
515 static_assert(THREAD_PRIORITY_ABOVE_NORMAL >= 0,
516 "THREAD_PRIORITY_ABOVE_NORMAL is < 0 and will incorrectly be "
517 "translated to ThreadPriorityForTest::kBackground.");
518 static_assert(THREAD_PRIORITY_HIGHEST >= 0,
519 "THREAD_PRIORITY_HIGHEST is < 0 and will incorrectly be "
520 "translated to ThreadPriorityForTest::kBackground.");
521 static_assert(THREAD_PRIORITY_TIME_CRITICAL >= 0,
522 "THREAD_PRIORITY_TIME_CRITICAL is < 0 and will incorrectly be "
523 "translated to ThreadPriorityForTest::kBackground.");
524 static_assert(THREAD_PRIORITY_ERROR_RETURN >= 0,
525 "THREAD_PRIORITY_ERROR_RETURN is < 0 and will incorrectly be "
526 "translated to ThreadPriorityForTest::kBackground.");
527
528 const int priority =
529 ::GetThreadPriority(PlatformThread::CurrentHandle().platform_handle());
530
531 // Negative values represent a background priority. We have observed -3, -4,
532 // -6 when THREAD_MODE_BACKGROUND_* is used. THREAD_PRIORITY_IDLE,
533 // THREAD_PRIORITY_LOWEST and THREAD_PRIORITY_BELOW_NORMAL are other possible
534 // negative values.
535 if (priority < THREAD_PRIORITY_BELOW_NORMAL)
536 return ThreadPriorityForTest::kBackground;
537
538 switch (priority) {
539 case THREAD_PRIORITY_BELOW_NORMAL:
540 return ThreadPriorityForTest::kUtility;
541 case THREAD_PRIORITY_NORMAL:
542 return ThreadPriorityForTest::kNormal;
543 case kWinDisplayPriority1:
544 [[fallthrough]];
545 case kWinDisplayPriority2:
546 return ThreadPriorityForTest::kDisplay;
547 case THREAD_PRIORITY_ABOVE_NORMAL:
548 case THREAD_PRIORITY_HIGHEST:
549 return ThreadPriorityForTest::kDisplay;
550 case THREAD_PRIORITY_TIME_CRITICAL:
551 return ThreadPriorityForTest::kRealtimeAudio;
552 case THREAD_PRIORITY_ERROR_RETURN:
553 DPCHECK(false) << "::GetThreadPriority error";
554 }
555
556 NOTREACHED() << "::GetThreadPriority returned " << priority << ".";
557 return ThreadPriorityForTest::kNormal;
558 }
559
InitializePlatformThreadFeatures()560 void InitializePlatformThreadFeatures() {
561 g_use_thread_priority_lowest.store(
562 FeatureList::IsEnabled(kUseThreadPriorityLowest),
563 std::memory_order_relaxed);
564 g_above_normal_compositing_browser.store(
565 FeatureList::IsEnabled(kAboveNormalCompositingBrowserWin),
566 std::memory_order_relaxed);
567 }
568
569 // static
GetDefaultThreadStackSize()570 size_t PlatformThread::GetDefaultThreadStackSize() {
571 return 0;
572 }
573
574 } // namespace base
575