| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "base/threading/platform_thread.h" | |
| #include <stddef.h> | |
| #include <array> | |
| #include "base/compiler_specific.h" | |
| #include "base/message_loop/message_pump_type.h" | |
| #include "base/process/process.h" | |
| #include "base/synchronization/waitable_event.h" | |
| #include "base/task/thread_type.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "base/threading/platform_thread_internal.h" | |
| #include "base/threading/thread.h" | |
| #include "base/threading/threading_features.h" | |
| #include "build/blink_buildflags.h" | |
| #include "build/build_config.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace base { | |
| std::ostream& operator<<(std::ostream& os, ThreadType type) { | |
| return os << ThreadTypeToString(type); | |
| } | |
| std::ostream& operator<<(std::ostream& os, MessagePumpType type) { | |
| switch (type) { | |
| case MessagePumpType::DEFAULT: | |
| return os << "DEFAULT"; | |
| case MessagePumpType::UI: | |
| return os << "UI"; | |
| case MessagePumpType::CUSTOM: | |
| return os << "CUSTOM"; | |
| case MessagePumpType::IO: | |
| return os << "IO"; | |
| #if BUILDFLAG(IS_ANDROID) | |
| case MessagePumpType::JAVA: | |
| return os << "JAVA"; | |
| #endif | |
| #if BUILDFLAG(IS_APPLE) | |
| case MessagePumpType::NS_RUNLOOP: | |
| return os << "NS_RUNLOOP"; | |
| #endif | |
| } | |
| return os << "Unknown(" << static_cast<int>(type) << ")"; | |
| } | |
| } // namespace base | |
| #if BUILDFLAG(IS_POSIX) | |
| #include "base/threading/platform_thread_internal_posix.h" | |
| #elif BUILDFLAG(IS_WIN) | |
| #include <windows.h> | |
| #include "base/threading/platform_thread_win.h" | |
| #endif | |
| #if BUILDFLAG(IS_APPLE) | |
| #include <mach/mach.h> | |
| #include <mach/mach_time.h> | |
| #include <mach/thread_policy.h> | |
| #include "base/mac/mac_util.h" | |
| #include "base/metrics/field_trial_params.h" | |
| #include "base/time/time.h" | |
| #endif | |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| #include <pthread.h> | |
| #include <sys/syscall.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #endif | |
| #if BUILDFLAG(IS_ANDROID) | |
| #include "base/system/sys_info.h" | |
| #endif | |
| using ::testing::_; | |
| using ::testing::InSequence; | |
| namespace base { | |
| // Trivial tests that thread runs and doesn't crash on create, join, or detach - | |
| namespace { | |
| class TrivialThread : public PlatformThread::Delegate { | |
| public: | |
| TrivialThread() | |
| : run_event_(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED) {} | |
| TrivialThread(const TrivialThread&) = delete; | |
| TrivialThread& operator=(const TrivialThread&) = delete; | |
| void ThreadMain() override { run_event_.Signal(); } | |
| WaitableEvent& run_event() { return run_event_; } | |
| private: | |
| WaitableEvent run_event_; | |
| }; | |
| } // namespace | |
| TEST(PlatformThreadTest, TrivialJoin) { | |
| TrivialThread thread; | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.run_event().IsSignaled()); | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | |
| PlatformThread::Join(handle); | |
| ASSERT_TRUE(thread.run_event().IsSignaled()); | |
| } | |
| TEST(PlatformThreadTest, TrivialJoinTimesTen) { | |
| std::array<TrivialThread, 10> thread; | |
| std::array<PlatformThreadHandle, std::size(thread)> handle; | |
| for (auto& n : thread) { | |
| ASSERT_FALSE(n.run_event().IsSignaled()); | |
| } | |
| for (size_t n = 0; n < std::size(thread); n++) { | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); | |
| } | |
| for (auto n : handle) { | |
| PlatformThread::Join(n); | |
| } | |
| for (auto& n : thread) { | |
| ASSERT_TRUE(n.run_event().IsSignaled()); | |
| } | |
| } | |
| // The following detach tests are by nature racy. The run_event approximates the | |
| // end and termination of the thread, but threads could persist shortly after | |
| // the test completes. | |
| TEST(PlatformThreadTest, TrivialDetach) { | |
| TrivialThread thread; | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.run_event().IsSignaled()); | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | |
| PlatformThread::Detach(handle); | |
| thread.run_event().Wait(); | |
| } | |
| TEST(PlatformThreadTest, TrivialDetachTimesTen) { | |
| std::array<TrivialThread, 10> thread; | |
| std::array<PlatformThreadHandle, std::size(thread)> handle; | |
| for (auto& n : thread) { | |
| ASSERT_FALSE(n.run_event().IsSignaled()); | |
| } | |
| for (size_t n = 0; n < std::size(thread); n++) { | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); | |
| PlatformThread::Detach(handle[n]); | |
| } | |
| for (auto& n : thread) { | |
| n.run_event().Wait(); | |
| } | |
| } | |
| // Tests of basic thread functions --------------------------------------------- | |
| namespace { | |
| class FunctionTestThread : public PlatformThread::Delegate { | |
| public: | |
| FunctionTestThread() | |
| : thread_id_(kInvalidThreadId), | |
| termination_ready_(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED), | |
| terminate_thread_(WaitableEvent::ResetPolicy::MANUAL, | |
| WaitableEvent::InitialState::NOT_SIGNALED) {} | |
| FunctionTestThread(const FunctionTestThread&) = delete; | |
| FunctionTestThread& operator=(const FunctionTestThread&) = delete; | |
| ~FunctionTestThread() override { | |
| EXPECT_TRUE(terminate_thread_.IsSignaled()) | |
| << "Need to mark thread for termination and join the underlying thread " | |
| << "before destroying a FunctionTestThread as it owns the " | |
| << "WaitableEvent blocking the underlying thread's main."; | |
| } | |
| // Grabs |thread_id_|, runs an optional test on that thread, signals | |
| // |termination_ready_|, and then waits for |terminate_thread_| to be | |
| // signaled before exiting. | |
| void ThreadMain() override { | |
| thread_id_ = PlatformThread::CurrentId(); | |
| EXPECT_NE(thread_id_, kInvalidThreadId); | |
| // Make sure that the thread ID is the same across calls. | |
| EXPECT_EQ(thread_id_, PlatformThread::CurrentId()); | |
| // Run extra tests. | |
| RunTest(); | |
| termination_ready_.Signal(); | |
| terminate_thread_.Wait(); | |
| done_ = true; | |
| } | |
| PlatformThreadId thread_id() const { | |
| EXPECT_TRUE(termination_ready_.IsSignaled()) << "Thread ID still unknown"; | |
| return thread_id_; | |
| } | |
| bool IsRunning() const { return termination_ready_.IsSignaled() && !done_; } | |
| // Blocks until this thread is started and ready to be terminated. | |
| void WaitForTerminationReady() { termination_ready_.Wait(); } | |
| // Marks this thread for termination (callers must then join this thread to be | |
| // guaranteed of termination). | |
| void MarkForTermination() { terminate_thread_.Signal(); } | |
| private: | |
| // Runs an optional test on the newly created thread. | |
| virtual void RunTest() {} | |
| PlatformThreadId thread_id_; | |
| mutable WaitableEvent termination_ready_; | |
| WaitableEvent terminate_thread_; | |
| bool done_ = false; | |
| }; | |
| } // namespace | |
| TEST(PlatformThreadTest, Function) { | |
| PlatformThreadId main_thread_id = PlatformThread::CurrentId(); | |
| FunctionTestThread thread; | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.IsRunning()); | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | |
| thread.WaitForTerminationReady(); | |
| ASSERT_TRUE(thread.IsRunning()); | |
| EXPECT_NE(thread.thread_id(), main_thread_id); | |
| thread.MarkForTermination(); | |
| PlatformThread::Join(handle); | |
| ASSERT_FALSE(thread.IsRunning()); | |
| // Make sure that the thread ID is the same across calls. | |
| EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); | |
| } | |
| TEST(PlatformThreadTest, FunctionTimesTen) { | |
| PlatformThreadId main_thread_id = PlatformThread::CurrentId(); | |
| std::array<FunctionTestThread, 10> thread; | |
| std::array<PlatformThreadHandle, std::size(thread)> handle; | |
| for (const auto& n : thread) { | |
| ASSERT_FALSE(n.IsRunning()); | |
| } | |
| for (size_t n = 0; n < std::size(thread); n++) { | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n])); | |
| } | |
| for (auto& n : thread) { | |
| n.WaitForTerminationReady(); | |
| } | |
| for (size_t n = 0; n < std::size(thread); n++) { | |
| ASSERT_TRUE(thread[n].IsRunning()); | |
| EXPECT_NE(thread[n].thread_id(), main_thread_id); | |
| // Make sure no two threads get the same ID. | |
| for (size_t i = 0; i < n; ++i) { | |
| EXPECT_NE(thread[i].thread_id(), thread[n].thread_id()); | |
| } | |
| } | |
| for (auto& n : thread) { | |
| n.MarkForTermination(); | |
| } | |
| for (auto n : handle) { | |
| PlatformThread::Join(n); | |
| } | |
| for (const auto& n : thread) { | |
| ASSERT_FALSE(n.IsRunning()); | |
| } | |
| // Make sure that the thread ID is the same across calls. | |
| EXPECT_EQ(main_thread_id, PlatformThread::CurrentId()); | |
| } | |
| namespace { | |
| constexpr ThreadType kAllThreadTypes[] = { | |
| ThreadType::kRealtimeAudio, ThreadType::kAudioProcessing, | |
| ThreadType::kPresentation, ThreadType::kDefault, | |
| ThreadType::kUtility, ThreadType::kBackground}; | |
| class ThreadTypeTestThread : public FunctionTestThread { | |
| public: | |
| explicit ThreadTypeTestThread(ThreadType from, ThreadType to) | |
| : from_(from), to_(to) {} | |
| ThreadTypeTestThread(const ThreadTypeTestThread&) = delete; | |
| ThreadTypeTestThread& operator=(const ThreadTypeTestThread&) = delete; | |
| ~ThreadTypeTestThread() override = default; | |
| private: | |
| void RunTest() override { | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault); | |
| PlatformThread::SetDefaultThreadType(from_); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from_); | |
| PlatformThread::SetDefaultThreadType(to_); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), to_); | |
| } | |
| const ThreadType from_; | |
| const ThreadType to_; | |
| }; | |
| class ThreadPriorityTestThread : public FunctionTestThread { | |
| public: | |
| ThreadPriorityTestThread(ThreadType thread_type, ThreadType priority) | |
| : thread_type_(thread_type), priority(priority) {} | |
| private: | |
| void RunTest() override { | |
| testing::Message message; | |
| message << "thread_type: " << static_cast<int>(thread_type_); | |
| SCOPED_TRACE(message); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault); | |
| PlatformThread::SetDefaultThreadType(thread_type_); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), thread_type_); | |
| if (PlatformThread::CanChangeThreadType(ThreadType::kDefault, | |
| thread_type_)) { | |
| EXPECT_EQ(PlatformThread::GetCurrentEffectiveThreadTypeForTest(), | |
| priority); | |
| } | |
| } | |
| const ThreadType thread_type_; | |
| const ThreadType priority; | |
| }; | |
| void TestSetDefaultThreadType() { | |
| for (auto from : kAllThreadTypes) { | |
| if (!PlatformThread::CanChangeThreadType(ThreadType::kDefault, from)) { | |
| continue; | |
| } | |
| for (auto to : kAllThreadTypes) { | |
| ThreadTypeTestThread thread(from, to); | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.IsRunning()); | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | |
| thread.WaitForTerminationReady(); | |
| ASSERT_TRUE(thread.IsRunning()); | |
| thread.MarkForTermination(); | |
| PlatformThread::Join(handle); | |
| ASSERT_FALSE(thread.IsRunning()); | |
| } | |
| } | |
| } | |
| void TestPriorityResultingFromThreadType(ThreadType thread_type, | |
| ThreadType priority) { | |
| ThreadPriorityTestThread thread(thread_type, priority); | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.IsRunning()); | |
| ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | |
| thread.WaitForTerminationReady(); | |
| ASSERT_TRUE(thread.IsRunning()); | |
| thread.MarkForTermination(); | |
| PlatformThread::Join(handle); | |
| ASSERT_FALSE(thread.IsRunning()); | |
| } | |
| } // namespace | |
| // Test changing a created thread's type. | |
| TEST(PlatformThreadTest, SetDefaultThreadType) { | |
| TestSetDefaultThreadType(); | |
| } | |
| #if BUILDFLAG(IS_WIN) | |
| // Test changing a created thread's priority in an IDLE_PRIORITY_CLASS process | |
| // (regression test for https://crbug.com/901483). | |
| TEST(PlatformThreadTest, | |
| SetDefaultThreadTypeWithThreadModeBackgroundIdleProcess) { | |
| ::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS); | |
| TestSetDefaultThreadType(); | |
| ::SetPriorityClass(Process::Current().Handle(), NORMAL_PRIORITY_CLASS); | |
| } | |
| #endif // BUILDFLAG(IS_WIN) | |
| // Ideally PlatformThread::CanChangeThreadType() would be true on all | |
| // platforms for all priorities. This not being the case. This test documents | |
| // and hardcodes what we know. Please inform scheduler-dev@chromium.org if this | |
| // proprerty changes for a given platform. | |
| TEST(PlatformThreadTest, CanChangeThreadType) { | |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| // On Ubuntu, RLIMIT_NICE and RLIMIT_RTPRIO are 0 by default, so we won't be | |
| // able to increase priority to any level unless we are root (euid == 0). | |
| bool kCanIncreasePriority = false; | |
| if (geteuid() == 0) { | |
| kCanIncreasePriority = true; | |
| } | |
| #else | |
| constexpr bool kCanIncreasePriority = true; | |
| #endif | |
| for (auto type : kAllThreadTypes) { | |
| EXPECT_TRUE(PlatformThread::CanChangeThreadType(type, type)); | |
| } | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kUtility)); | |
| EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kDefault)); | |
| EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kDefault, | |
| ThreadType::kBackground)); | |
| #else | |
| EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kUtility), | |
| kCanIncreasePriority); | |
| EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kDefault), | |
| kCanIncreasePriority); | |
| EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kDefault, | |
| ThreadType::kBackground)); | |
| #endif | |
| EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kPresentation), | |
| kCanIncreasePriority); | |
| EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground, | |
| ThreadType::kRealtimeAudio), | |
| kCanIncreasePriority); | |
| #if BUILDFLAG(IS_FUCHSIA) | |
| EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kPresentation, | |
| ThreadType::kBackground)); | |
| EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kRealtimeAudio, | |
| ThreadType::kBackground)); | |
| #else | |
| EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kPresentation, | |
| ThreadType::kBackground)); | |
| EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kRealtimeAudio, | |
| ThreadType::kBackground)); | |
| #endif | |
| } | |
| TEST(PlatformThreadTest, SetDefaultThreadTypeTest) { | |
| TestPriorityResultingFromThreadType(ThreadType::kBackground, | |
| ThreadType::kBackground); | |
| TestPriorityResultingFromThreadType(ThreadType::kUtility, | |
| ThreadType::kUtility); | |
| TestPriorityResultingFromThreadType(ThreadType::kDefault, | |
| ThreadType::kDefault); | |
| TestPriorityResultingFromThreadType(ThreadType::kPresentation, | |
| ThreadType::kPresentation); | |
| TestPriorityResultingFromThreadType(ThreadType::kRealtimeAudio, | |
| ThreadType::kRealtimeAudio); | |
| #if BUILDFLAG(IS_WIN) | |
| // Currently only on Windows, kInteractive maps to a higher priority than | |
| // kDisplayCritical. | |
| TestPriorityResultingFromThreadType(ThreadType::kAudioProcessing, | |
| ThreadType::kAudioProcessing); | |
| #else | |
| // On other platforms, kInteractive maps to the same priority as | |
| // kDisplayCritical. | |
| TestPriorityResultingFromThreadType(ThreadType::kAudioProcessing, | |
| ThreadType::kPresentation); | |
| #endif | |
| } | |
| TEST(PlatformThreadTest, SetHugeThreadName) { | |
| // Construct an excessively long thread name. | |
| std::string long_name(1024, 'a'); | |
| // SetName has no return code, so just verify that implementations | |
| // don't [D]CHECK(). | |
| PlatformThread::SetName(long_name); | |
| } | |
| TEST(PlatformThreadTest, GetDefaultThreadStackSize) { | |
| size_t stack_size = PlatformThread::GetDefaultThreadStackSize(); | |
| #if BUILDFLAG(IS_IOS) | |
| EXPECT_EQ(1024u * 1024u, stack_size); | |
| #elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA) || \ | |
| ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(__GLIBC__) && \ | |
| !defined(THREAD_SANITIZER)) || \ | |
| (BUILDFLAG(IS_ANDROID) && !defined(ADDRESS_SANITIZER)) | |
| EXPECT_EQ(0u, stack_size); | |
| #else | |
| EXPECT_GT(stack_size, 0u); | |
| EXPECT_LT(stack_size, 20u * (1 << 20)); | |
| #endif | |
| } | |
| #if BUILDFLAG(IS_APPLE) | |
| namespace { | |
| class RealtimeTestThread : public FunctionTestThread { | |
| public: | |
| explicit RealtimeTestThread(TimeDelta realtime_period) | |
| : realtime_period_(realtime_period) {} | |
| ~RealtimeTestThread() override = default; | |
| private: | |
| RealtimeTestThread(const RealtimeTestThread&) = delete; | |
| RealtimeTestThread& operator=(const RealtimeTestThread&) = delete; | |
| TimeDelta GetRealtimePeriod() final { return realtime_period_; } | |
| // Verifies the realtime thead configuration. | |
| void RunTest() override { | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kRealtimeAudio); | |
| mach_port_t mach_thread_id = pthread_mach_thread_np( | |
| PlatformThread::CurrentHandle().platform_handle()); | |
| // |count| and |get_default| chosen impirically so that | |
| // time_constraints_buffer[0] would store the last constraints that were | |
| // applied. | |
| const int kPolicyCount = 32; | |
| thread_time_constraint_policy_data_t time_constraints_buffer[kPolicyCount]; | |
| mach_msg_type_number_t count = kPolicyCount; | |
| boolean_t get_default = 0; | |
| kern_return_t result = thread_policy_get( | |
| mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY, | |
| reinterpret_cast<thread_policy_t>(time_constraints_buffer), &count, | |
| &get_default); | |
| EXPECT_EQ(result, KERN_SUCCESS); | |
| const thread_time_constraint_policy_data_t& time_constraints = | |
| time_constraints_buffer[0]; | |
| mach_timebase_info_data_t tb_info; | |
| mach_timebase_info(&tb_info); | |
| if (FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac) && | |
| !realtime_period_.is_zero()) { | |
| uint32_t abs_realtime_period = saturated_cast<uint32_t>( | |
| realtime_period_.InNanoseconds() * | |
| (static_cast<double>(tb_info.denom) / tb_info.numer)); | |
| EXPECT_EQ(time_constraints.period, abs_realtime_period); | |
| EXPECT_EQ( | |
| time_constraints.computation, | |
| static_cast<uint32_t>(abs_realtime_period * | |
| kOptimizedRealtimeThreadingMacBusy.Get())); | |
| EXPECT_EQ( | |
| time_constraints.constraint, | |
| static_cast<uint32_t>(abs_realtime_period * | |
| kOptimizedRealtimeThreadingMacBusyLimit.Get())); | |
| EXPECT_EQ(time_constraints.preemptible, | |
| kOptimizedRealtimeThreadingMacPreemptible.Get()); | |
| } else { | |
| // Old-style empirical values. | |
| const double kTimeQuantum = 2.9; | |
| const double kAudioTimeNeeded = 0.75 * kTimeQuantum; | |
| const double kMaxTimeAllowed = 0.85 * kTimeQuantum; | |
| // Get the conversion factor from milliseconds to absolute time | |
| // which is what the time-constraints returns. | |
| double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000; | |
| EXPECT_EQ(time_constraints.period, | |
| saturated_cast<uint32_t>(kTimeQuantum * ms_to_abs_time)); | |
| EXPECT_EQ(time_constraints.computation, | |
| saturated_cast<uint32_t>(kAudioTimeNeeded * ms_to_abs_time)); | |
| EXPECT_EQ(time_constraints.constraint, | |
| saturated_cast<uint32_t>(kMaxTimeAllowed * ms_to_abs_time)); | |
| EXPECT_FALSE(time_constraints.preemptible); | |
| } | |
| } | |
| const TimeDelta realtime_period_; | |
| }; | |
| class RealtimePlatformThreadTest | |
| : public testing::TestWithParam< | |
| std::tuple<bool, FieldTrialParams, TimeDelta>> { | |
| protected: | |
| void VerifyRealtimeConfig(TimeDelta period) { | |
| RealtimeTestThread thread(period); | |
| PlatformThreadHandle handle; | |
| ASSERT_FALSE(thread.IsRunning()); | |
| ASSERT_TRUE(PlatformThread::CreateWithType(0, &thread, &handle, | |
| ThreadType::kRealtimeAudio)); | |
| thread.WaitForTerminationReady(); | |
| ASSERT_TRUE(thread.IsRunning()); | |
| thread.MarkForTermination(); | |
| PlatformThread::Join(handle); | |
| ASSERT_FALSE(thread.IsRunning()); | |
| } | |
| }; | |
| TEST_P(RealtimePlatformThreadTest, RealtimeAudioConfigMac) { | |
| test::ScopedFeatureList feature_list; | |
| if (std::get<0>(GetParam())) { | |
| feature_list.InitAndEnableFeatureWithParameters( | |
| kOptimizedRealtimeThreadingMac, std::get<1>(GetParam())); | |
| } else { | |
| feature_list.InitAndDisableFeature(kOptimizedRealtimeThreadingMac); | |
| } | |
| PlatformThread::InitializeFeatures(); | |
| VerifyRealtimeConfig(std::get<2>(GetParam())); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| RealtimePlatformThreadTest, | |
| RealtimePlatformThreadTest, | |
| testing::Combine( | |
| testing::Bool(), | |
| testing::Values( | |
| FieldTrialParams{ | |
| {kOptimizedRealtimeThreadingMacPreemptible.name, "true"}}, | |
| FieldTrialParams{ | |
| {kOptimizedRealtimeThreadingMacPreemptible.name, "false"}}, | |
| FieldTrialParams{ | |
| {kOptimizedRealtimeThreadingMacBusy.name, "0.5"}, | |
| {kOptimizedRealtimeThreadingMacBusyLimit.name, "0.75"}}, | |
| FieldTrialParams{ | |
| {kOptimizedRealtimeThreadingMacBusy.name, "0.7"}, | |
| {kOptimizedRealtimeThreadingMacBusyLimit.name, "0.7"}}, | |
| FieldTrialParams{ | |
| {kOptimizedRealtimeThreadingMacBusy.name, "0.5"}, | |
| {kOptimizedRealtimeThreadingMacBusyLimit.name, "1.0"}}), | |
| testing::Values(TimeDelta(), | |
| Seconds(256.0 / 48000), | |
| Milliseconds(5), | |
| Milliseconds(10), | |
| Seconds(1024.0 / 44100), | |
| Seconds(1024.0 / 16000)))); | |
| } // namespace | |
| #endif // BUILDFLAG(IS_APPLE) | |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| namespace { | |
| bool IsTidCacheCorrect() { | |
| return PlatformThread::CurrentId().raw() == syscall(__NR_gettid); | |
| } | |
| void* CheckTidCacheCorrectWrapper(void*) { | |
| CHECK(IsTidCacheCorrect()); | |
| return nullptr; | |
| } | |
| void CreatePthreadToCheckCache() { | |
| pthread_t thread_id; | |
| pthread_create(&thread_id, nullptr, CheckTidCacheCorrectWrapper, nullptr); | |
| pthread_join(thread_id, nullptr); | |
| } | |
| // This test must use raw pthreads and fork() to avoid calls from //base to | |
| // PlatformThread::CurrentId(), as the ordering of calls is important to the | |
| // test. | |
| void TestTidCacheCorrect(bool main_thread_accesses_cache_first) { | |
| EXPECT_TRUE(IsTidCacheCorrect()); | |
| CreatePthreadToCheckCache(); | |
| // Now fork a process and make sure the TID cache gets correctly updated on | |
| // both its main thread and a child thread. | |
| pid_t child_pid = fork(); | |
| ASSERT_GE(child_pid, 0); | |
| if (child_pid == 0) { | |
| // In the child. | |
| if (main_thread_accesses_cache_first) { | |
| if (!IsTidCacheCorrect()) { | |
| _exit(1); | |
| } | |
| } | |
| // Access the TID cache on another thread and make sure the cached value is | |
| // correct. | |
| CreatePthreadToCheckCache(); | |
| if (!main_thread_accesses_cache_first) { | |
| // Make sure the main thread's cache is correct even though another thread | |
| // accessed the cache first. | |
| if (!IsTidCacheCorrect()) { | |
| _exit(1); | |
| } | |
| } | |
| _exit(0); | |
| } | |
| int status; | |
| ASSERT_EQ(waitpid(child_pid, &status, 0), child_pid); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(WEXITSTATUS(status), 0); | |
| } | |
| TEST(PlatformThreadTidCacheTest, MainThreadFirst) { | |
| TestTidCacheCorrect(true); | |
| } | |
| TEST(PlatformThreadTidCacheTest, MainThreadSecond) { | |
| TestTidCacheCorrect(false); | |
| } | |
| } // namespace | |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) | |
| #if BUILDFLAG(IS_ANDROID) | |
| namespace { | |
| // Helper function to put all the UNSAFE_BUFFERS() in a single place. | |
| int NumberOfAllowedProcessors() { | |
| cpu_set_t cpuset; | |
| // SAFETY: Here and below, these macros are part of system headers, and we | |
| // cannot assume their content, however it checks the bounds. | |
| UNSAFE_BUFFERS(CPU_ZERO(&cpuset)); | |
| EXPECT_EQ(0, sched_getaffinity(0, sizeof(cpu_set_t), &cpuset)); | |
| return UNSAFE_BUFFERS(CPU_COUNT(&cpuset)); | |
| } | |
| } // namespace | |
| TEST(PlatformThreadCpuAffinity, DefaultToAllCores) { | |
| if (!IsEligibleForBigCoreAffinityChange()) { | |
| GTEST_SKIP(); | |
| } | |
| test::ScopedFeatureList feature_list{kRestrictBigCoreThreadAffinity}; | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| } | |
| TEST(PlatformThreadCpuAffinity, RestrictAffinity) { | |
| // Need at least three distinct classes to test affinity, skip if there are | |
| // fewer CPUs than that. | |
| if (SysInfo::NumberOfProcessors() < 3) { | |
| GTEST_SKIP(); | |
| } | |
| std::vector<uint64_t> fake_frequencies = SysInfo::MaxFrequencyPerProcessor(); | |
| if (fake_frequencies.empty()) { | |
| GTEST_SKIP() << "Cannot determine frequencies. This can happen in VMs for " | |
| << "instance"; | |
| } | |
| for (size_t i = 0; i < fake_frequencies.size(); i++) { | |
| fake_frequencies[i] = static_cast<uint64_t>(1000000000) + (i * 100000000); | |
| } | |
| SetMaxFrequencyPerProcessorOverrideForTesting(&fake_frequencies); | |
| ASSERT_TRUE(IsEligibleForBigCoreAffinityChange()); | |
| { | |
| test::ScopedFeatureList feature_list{kRestrictBigCoreThreadAffinity}; | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kBackground); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors() - 1, NumberOfAllowedProcessors()); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kPresentation); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kDefault); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors() - 1, NumberOfAllowedProcessors()); | |
| // Make sure that affinity is reset to everything, as when the feature is | |
| // disabled, the affinity will stay to the value it had previously. | |
| PlatformThread::SetDefaultThreadType(ThreadType::kPresentation); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| } | |
| { | |
| test::ScopedFeatureList feature_list; | |
| feature_list.InitAndDisableFeature(kRestrictBigCoreThreadAffinity); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kBackground); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kDefault); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| } | |
| SetMaxFrequencyPerProcessorOverrideForTesting(nullptr); | |
| } | |
| TEST(PlatformThreadCpuAffinity, RestrictAffinityNoopWithTwoCoreTypes) { | |
| // Only two core types. | |
| std::vector<uint64_t> fake_frequencies = SysInfo::MaxFrequencyPerProcessor(); | |
| for (size_t i = 0; i < fake_frequencies.size(); i++) { | |
| fake_frequencies[i] = i % 2 ? 1000000000 : 1500000000; | |
| } | |
| SetMaxFrequencyPerProcessorOverrideForTesting(&fake_frequencies); | |
| EXPECT_FALSE(IsEligibleForBigCoreAffinityChange()); | |
| test::ScopedFeatureList feature_list{kRestrictBigCoreThreadAffinity}; | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| PlatformThread::SetDefaultThreadType(ThreadType::kBackground); | |
| EXPECT_EQ(SysInfo::NumberOfProcessors(), NumberOfAllowedProcessors()); | |
| SetMaxFrequencyPerProcessorOverrideForTesting(nullptr); | |
| } | |
| TEST(PlatformThreadCpuAffinity, IsEligibleForBigCoreAffinityChange) { | |
| std::vector<uint64_t> fake_frequencies; | |
| SetMaxFrequencyPerProcessorOverrideForTesting(&fake_frequencies); | |
| EXPECT_FALSE(IsEligibleForBigCoreAffinityChange()); | |
| fake_frequencies = {1000, 2000}; | |
| EXPECT_FALSE(IsEligibleForBigCoreAffinityChange()); | |
| fake_frequencies = {1000, 2000, 3000}; | |
| EXPECT_TRUE(IsEligibleForBigCoreAffinityChange()); | |
| fake_frequencies = {1000, 2000, 2000, 3000}; | |
| EXPECT_TRUE(IsEligibleForBigCoreAffinityChange()); | |
| fake_frequencies = {1000, 1000, 1000}; | |
| EXPECT_FALSE(IsEligibleForBigCoreAffinityChange()); | |
| SetMaxFrequencyPerProcessorOverrideForTesting(nullptr); | |
| } | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| class PlatformThreadThreadTypeManagerTest : public ::testing::Test, | |
| public internal::ThreadTypeManager { | |
| public: | |
| std::unique_ptr<PlatformThread::RaiseThreadTypeLease> CreateLease( | |
| ThreadType type) { | |
| return std::unique_ptr<PlatformThread::RaiseThreadTypeLease>( | |
| new PlatformThread::RaiseThreadTypeLease(type, this)); | |
| } | |
| void SetCurrentThreadTypeImpl(ThreadType type, | |
| MessagePumpType pump_type) override { | |
| mock_set_thread_type_.Call(type, pump_type); | |
| } | |
| testing::MockFunction<void(ThreadType, MessagePumpType)> | |
| mock_set_thread_type_; | |
| }; | |
| TEST_F(PlatformThreadThreadTypeManagerTest, HasDefaultThreadTypeInitialState) { | |
| EXPECT_CALL(mock_set_thread_type_, Call).Times(0); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kDefault); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, CanDepressPriority) { | |
| EXPECT_CALL(mock_set_thread_type_, Call).Times(1); | |
| SetDefault(ThreadType::kBackground); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kBackground); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, CanElevatePriority) { | |
| EXPECT_CALL(mock_set_thread_type_, Call).Times(1); | |
| SetDefault(ThreadType::kPresentation); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, | |
| CanLeaseLowerThanDefaultPriorityWithUnsetBase) { | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kBackground, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| auto lease = CreateLease(ThreadType::kBackground); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kBackground); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, RaisesPriorityWithLeaseScoped) { | |
| InSequence s; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| { | |
| auto lease = CreateLease(ThreadType::kPresentation); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kDefault); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, IgnoresLowerPriorityLease) { | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| SetDefault(ThreadType::kPresentation); | |
| { | |
| auto lease = CreateLease(ThreadType::kDefault); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, HandlesMultipleIdenticalLeases) { | |
| InSequence s; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| { | |
| auto lease1 = CreateLease(ThreadType::kPresentation); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| { | |
| auto lease2 = CreateLease(ThreadType::kPresentation); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kDefault); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, HandlesMultipleDistinctLeases) { | |
| InSequence s; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kRealtimeAudio, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| { | |
| auto lease1 = CreateLease(ThreadType::kPresentation); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| { | |
| auto lease2 = CreateLease(ThreadType::kRealtimeAudio); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kRealtimeAudio); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kPresentation); | |
| } | |
| EXPECT_EQ(GetCurrent(), ThreadType::kDefault); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, LeaseTransferByMoveAssignment) { | |
| InSequence s; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| auto lease1 = CreateLease(ThreadType::kPresentation); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kRealtimeAudio, _)); | |
| auto lease2 = CreateLease(ThreadType::kRealtimeAudio); | |
| lease1 = std::move(lease2); | |
| EXPECT_EQ(GetCurrent(), ThreadType::kRealtimeAudio); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| } | |
| TEST_F(PlatformThreadThreadTypeManagerTest, LeaseCrossDrop) { | |
| InSequence s; | |
| std::unique_ptr<PlatformThread::RaiseThreadTypeLease> bg_lease; | |
| std::unique_ptr<PlatformThread::RaiseThreadTypeLease> audio_lease; | |
| std::unique_ptr<PlatformThread::RaiseThreadTypeLease> presentation_lease; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kBackground, _)); | |
| bg_lease = CreateLease(ThreadType::kBackground); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kRealtimeAudio, _)); | |
| audio_lease = CreateLease(ThreadType::kRealtimeAudio); | |
| // No call expected since audio_lease is active & kAudioProcessing > kDefault | |
| presentation_lease = CreateLease(ThreadType::kPresentation); | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kPresentation, _)); | |
| audio_lease = nullptr; | |
| // No call expected since presentation_lease is still active. | |
| bg_lease = nullptr; | |
| EXPECT_CALL(mock_set_thread_type_, Call(ThreadType::kDefault, _)); | |
| presentation_lease = nullptr; | |
| } | |
| TEST(PlatformThreadRaiseLeaseIntegrationTest, KeepsMaxThreadType) { | |
| { | |
| auto audio_lease = | |
| PlatformThread::RaiseThreadTypeLease(ThreadType::kAudioProcessing); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| { | |
| auto presentation_lease = | |
| PlatformThread::RaiseThreadTypeLease(ThreadType::kPresentation); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| } | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| } | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault); | |
| } | |
| TEST(PlatformThreadRaiseLeaseIntegrationTest, TemporaryDepression) { | |
| std::optional<PlatformThread::RaiseThreadTypeLease> audio_lease; | |
| audio_lease.emplace(ThreadType::kAudioProcessing); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| audio_lease = std::nullopt; | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault); | |
| audio_lease.emplace(ThreadType::kAudioProcessing); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| } | |
| TEST(PlatformThreadRaiseLeaseIntegrationTest, LeaseCrossDrop) { | |
| std::optional<PlatformThread::RaiseThreadTypeLease> audio_lease; | |
| std::optional<PlatformThread::RaiseThreadTypeLease> presentation_lease; | |
| audio_lease.emplace(ThreadType::kAudioProcessing); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| presentation_lease.emplace(ThreadType::kPresentation); | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), | |
| ThreadType::kAudioProcessing); | |
| audio_lease = std::nullopt; | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kPresentation); | |
| presentation_lease = std::nullopt; | |
| EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault); | |
| } | |
| } // namespace base |