#include "base/include/fml/platform/thread_config_setter.h"
#include "base/include/fml/synchronization/count_down_latch.h"
#include "base/include/fml/thread.h"
#include "build/build_config.h"
#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID)
#define FLUTTER_PTHREAD_SUPPORTED 1
#else
#define FLUTTER_PTHREAD_SUPPORTED 0
#endif
#if FLUTTER_PTHREAD_SUPPORTED
#include <pthread.h>
#else
#endif
#if defined(__APPLE__)
#include <pthread/qos.h>
#endif
#include <memory>
#include "third_party/googletest/googletest/include/gtest/gtest.h"
namespace lynx {
namespace {
bool EnableThreadSchedulingPolicyForTest() { return true; }
bool DisableThreadSchedulingPolicyForTest() { return false; }
class ScopedThreadSchedulingPolicyProvider {
public:
explicit ScopedThreadSchedulingPolicyProvider(
fml::PlatformThreadPriority::ThreadSchedulingPolicyEnabledProvider
provider) {
fml::PlatformThreadPriority::SetThreadSchedulingPolicyEnabledProvider(
provider);
}
~ScopedThreadSchedulingPolicyProvider() {
fml::PlatformThreadPriority::SetThreadSchedulingPolicyEnabledProvider(
nullptr);
}
};
}
TEST(Thread, CanStartAndEnd) {
fml::Thread thread;
ASSERT_TRUE(thread.GetTaskRunner());
}
TEST(Thread, CanStartAndEndWithExplicitJoin) {
fml::Thread thread;
ASSERT_TRUE(thread.GetTaskRunner());
thread.Join();
}
TEST(Thread, HasARunningMessageLoop) {
fml::Thread thread;
bool done = false;
thread.GetTaskRunner()->PostTask([&done]() { done = true; });
thread.Join();
ASSERT_TRUE(done);
}
TEST(Thread, ThreadSchedulingPolicyEnabledProvider) {
fml::PlatformThreadPriority::SetThreadSchedulingPolicyEnabledProvider(
nullptr);
ASSERT_FALSE(fml::PlatformThreadPriority::IsThreadSchedulingPolicyEnabled());
{
ScopedThreadSchedulingPolicyProvider provider(
EnableThreadSchedulingPolicyForTest);
ASSERT_TRUE(fml::PlatformThreadPriority::IsThreadSchedulingPolicyEnabled());
}
ASSERT_FALSE(fml::PlatformThreadPriority::IsThreadSchedulingPolicyEnabled());
{
ScopedThreadSchedulingPolicyProvider provider(
DisableThreadSchedulingPolicyForTest);
ASSERT_FALSE(
fml::PlatformThreadPriority::IsThreadSchedulingPolicyEnabled());
}
}
#if defined(__APPLE__)
TEST(Thread, PlatformThreadPriorityMatchesDarwinQoSClass) {
ASSERT_EQ(fml::PlatformThreadPriority::GetPlatformThreadPriority(
fml::Thread::ThreadPriority::BACKGROUND),
static_cast<int>(QOS_CLASS_BACKGROUND));
ASSERT_EQ(fml::PlatformThreadPriority::GetPlatformThreadPriority(
fml::Thread::ThreadPriority::LOW),
static_cast<int>(QOS_CLASS_BACKGROUND));
ASSERT_EQ(fml::PlatformThreadPriority::GetPlatformThreadPriority(
fml::Thread::ThreadPriority::NORMAL),
static_cast<int>(QOS_CLASS_DEFAULT));
ASSERT_EQ(fml::PlatformThreadPriority::GetPlatformThreadPriority(
fml::Thread::ThreadPriority::HIGH),
static_cast<int>(QOS_CLASS_USER_INITIATED));
}
TEST(Thread, ThreadSchedulingPolicyConfiguresDarwinThreadQoS) {
ScopedThreadSchedulingPolicyProvider provider(
EnableThreadSchedulingPolicyForTest);
fml::Thread thread(
fml::PlatformThreadPriority::Setter,
fml::Thread::ThreadConfig("qos_test", fml::Thread::ThreadPriority::HIGH));
fml::CountDownLatch latch(1);
qos_class_t qos_class = QOS_CLASS_UNSPECIFIED;
int relative_priority = QOS_MIN_RELATIVE_PRIORITY;
thread.GetTaskRunner()->PostTask([&]() {
pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority);
latch.CountDown();
});
latch.Wait();
thread.Join();
ASSERT_EQ(qos_class, QOS_CLASS_USER_INITIATED);
ASSERT_EQ(relative_priority, 0);
}
#endif
#if FLUTTER_PTHREAD_SUPPORTED
TEST(Thread, ThreadNameCreatedWithConfig) {
const std::string name = "Thread1";
fml::Thread thread(name);
bool done = false;
thread.GetTaskRunner()->PostTask([&done, &name]() {
done = true;
char thread_name[16];
pthread_t current_thread = pthread_self();
pthread_getname_np(current_thread, thread_name, sizeof(thread_name));
ASSERT_EQ(thread_name, name);
});
thread.Join();
ASSERT_TRUE(done);
}
static void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) {
fml::Thread::SetCurrentThreadName(config);
pthread_t tid = pthread_self();
struct sched_param param;
int policy = SCHED_OTHER;
switch (config.priority) {
case fml::Thread::ThreadPriority::HIGH:
param.sched_priority = 10;
break;
default:
param.sched_priority = 1;
}
#if defined(OS_LINUX)
param.sched_priority = 0;
#endif
pthread_setschedparam(tid, policy, ¶m);
}
TEST(Thread, ThreadPriorityCreatedWithConfig) {
const std::string thread1_name = "Thread1";
const std::string thread2_name = "Thread2";
fml::Thread thread(MockThreadConfigSetter,
fml::Thread::ThreadConfig(
thread1_name, fml::Thread::ThreadPriority::NORMAL));
bool done = false;
struct sched_param param;
int policy;
thread.GetTaskRunner()->PostTask([&]() {
done = true;
char thread_name[16];
pthread_t current_thread = pthread_self();
pthread_getname_np(current_thread, thread_name, sizeof(thread_name));
pthread_getschedparam(current_thread, &policy, ¶m);
ASSERT_EQ(thread_name, thread1_name);
ASSERT_EQ(policy, SCHED_OTHER);
#if !defined(OS_LINUX)
ASSERT_EQ(param.sched_priority, 1);
#else
ASSERT_EQ(param.sched_priority, 0);
#endif
});
struct sched_param param2;
fml::Thread thread2(MockThreadConfigSetter,
fml::Thread::ThreadConfig(
thread2_name, fml::Thread::ThreadPriority::HIGH));
thread2.GetTaskRunner()->PostTask([&]() {
done = true;
char thread_name[16];
pthread_t current_thread = pthread_self();
pthread_getname_np(current_thread, thread_name, sizeof(thread_name));
pthread_getschedparam(current_thread, &policy, ¶m2);
ASSERT_EQ(thread_name, thread2_name);
ASSERT_EQ(policy, SCHED_OTHER);
#if !defined(OS_LINUX)
ASSERT_EQ(param2.sched_priority, 10);
#else
ASSERT_EQ(param2.sched_priority, 0);
#endif
});
thread.Join();
ASSERT_TRUE(done);
}
#endif
}