#include "base/threading/scoped_thread_priority.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
#define ASSERT_RUNS_ONCE() \
static int num_times_run = 0; \
++num_times_run; \
if (num_times_run > 1) \
ADD_FAILURE() << "This test cannot run multiple times in the same " \
"process.";
static ThreadType kAllThreadTypes[] = {
ThreadType::kRealtimeAudio, ThreadType::kDisplayCritical,
ThreadType::kDefault, ThreadType::kBackground};
static_assert(static_cast<int>(ThreadType::kBackground) == 0,
"kBackground isn't lowest");
static_assert(ThreadType::kRealtimeAudio == ThreadType::kMaxValue,
"kRealtimeAudio isn't highest");
class ScopedThreadPriorityTest : public testing::Test {
protected:
void SetUp() override {
PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
ASSERT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
};
using ScopedThreadPriorityDeathTest = ScopedThreadPriorityTest;
#if BUILDFLAG(IS_WIN)
void FunctionThatBoostsPriorityOnFirstInvoke(ThreadType expected_priority) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
EXPECT_EQ(expected_priority,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
void FunctionThatBoostsPriorityOnEveryInvoke() {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
EXPECT_EQ(base::ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
#endif
}
TEST_F(ScopedThreadPriorityTest, BasicTest) {
for (auto from : kAllThreadTypes) {
if (!PlatformThread::CanChangeThreadType(ThreadType::kDefault, from)) {
continue;
}
for (auto to : kAllThreadTypes) {
if (to == ThreadType::kRealtimeAudio) {
continue;
}
Thread thread("ScopedThreadPriorityTest");
thread.StartWithOptions(Thread::Options(from));
thread.WaitUntilThreadStarted();
thread.task_runner()->PostTask(
FROM_HERE,
BindOnce(
[](ThreadType from, ThreadType to) {
EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from);
{
ScopedBoostPriority scoped_boost_priority(to);
bool will_boost_priority =
from < to &&
PlatformThread::CanChangeThreadType(from, to) &&
PlatformThread::CanChangeThreadType(to, from);
EXPECT_EQ(PlatformThread::GetCurrentThreadType(),
will_boost_priority ? to : from);
}
EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from);
},
from, to));
}
}
}
void TestPriorityResultingFromBoost(ThreadType initial_thread_type,
ThreadType target_thread_type) {
Thread thread("ScopedThreadPriorityTest");
thread.StartWithOptions(Thread::Options(initial_thread_type));
thread.WaitUntilThreadStarted();
WaitableEvent thread_ready;
WaitableEvent thread_boosted;
raw_ptr<ScopedBoostablePriority> scoped_boostable_priority_ptr;
bool will_boost_priority =
#if BUILDFLAG(IS_LINUX)
false;
#else
initial_thread_type < target_thread_type &&
PlatformThread::CanChangeThreadType(initial_thread_type,
target_thread_type) &&
PlatformThread::CanChangeThreadType(target_thread_type,
initial_thread_type);
#endif
thread.task_runner()->PostTask(
FROM_HERE, BindLambdaForTesting([&]() {
EXPECT_EQ(PlatformThread::GetCurrentThreadType(), initial_thread_type);
{
ScopedBoostablePriority scoped_boostable_priority;
scoped_boostable_priority_ptr = &scoped_boostable_priority;
thread_ready.Signal();
thread_boosted.Wait();
scoped_boostable_priority_ptr = nullptr;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
if (will_boost_priority) {
EXPECT_EQ(PlatformThread::GetCurrentEffectiveThreadTypeForTest(),
target_thread_type);
}
#endif
}
EXPECT_EQ(PlatformThread::GetCurrentThreadType(), initial_thread_type);
EXPECT_EQ(PlatformThread::GetCurrentEffectiveThreadTypeForTest(),
initial_thread_type);
}));
thread_ready.Wait();
bool did_boost_priority =
scoped_boostable_priority_ptr->BoostPriority(target_thread_type);
EXPECT_EQ(did_boost_priority, will_boost_priority);
thread_boosted.Signal();
thread.FlushForTesting();
}
TEST_F(ScopedThreadPriorityTest, BoostableTest) {
TestPriorityResultingFromBoost(ThreadType::kBackground, ThreadType::kUtility);
TestPriorityResultingFromBoost(ThreadType::kBackground, ThreadType::kDefault);
TestPriorityResultingFromBoost(ThreadType::kBackground,
ThreadType::kDisplayCritical);
TestPriorityResultingFromBoost(ThreadType::kUtility, ThreadType::kDefault);
TestPriorityResultingFromBoost(ThreadType::kUtility,
ThreadType::kDisplayCritical);
TestPriorityResultingFromBoost(ThreadType::kDefault,
ThreadType::kDisplayCritical);
}
TEST_F(ScopedThreadPriorityDeathTest, NoRealTime) {
EXPECT_CHECK_DEATH({
ScopedBoostPriority scoped_boost_priority(ThreadType::kRealtimeAudio);
});
}
TEST_F(ScopedThreadPriorityTest, WithoutPriorityBoost) {
ASSERT_RUNS_ONCE();
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
#if BUILDFLAG(IS_WIN)
TEST_F(ScopedThreadPriorityTest, WithPriorityBoost) {
ASSERT_RUNS_ONCE();
PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
EXPECT_EQ(ThreadType::kBackground,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
}
#endif
#if BUILDFLAG(IS_WIN)
TEST_F(ScopedThreadPriorityTest, NestedScope) {
ASSERT_RUNS_ONCE();
PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
EXPECT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
EXPECT_EQ(ThreadType::kBackground,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
}
#endif
#if BUILDFLAG(IS_WIN)
TEST_F(ScopedThreadPriorityTest, FunctionThatBoostsPriorityOnFirstInvoke) {
ASSERT_RUNS_ONCE();
PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
FunctionThatBoostsPriorityOnFirstInvoke(base::ThreadType::kDefault);
FunctionThatBoostsPriorityOnFirstInvoke(base::ThreadType::kBackground);
PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
}
TEST_F(ScopedThreadPriorityTest, FunctionThatBoostsPriorityOnEveryInvoke) {
PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
FunctionThatBoostsPriorityOnEveryInvoke();
FunctionThatBoostsPriorityOnEveryInvoke();
PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
}
TEST_F(ScopedThreadPriorityTest, TaskMonitoringBoost) {
ASSERT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
{
TaskMonitoringScopedBoostPriority scoped_boost_priority(
ThreadType::kInteractive, BindRepeating([]() { return true; }));
ASSERT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
scoped_boost_priority.WillProcessTask(PendingTask(), false);
ASSERT_EQ(ThreadType::kInteractive,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
ASSERT_EQ(ThreadType::kDefault,
PlatformThread::GetCurrentEffectiveThreadTypeForTest());
}
#endif
}