diff --git a/fml/thread.cc b/fml/thread.cc index 5eb05deaf978a..a06a6e940d7ea 100644 --- a/fml/thread.cc +++ b/fml/thread.cc @@ -8,6 +8,7 @@ #include #include +#include #include "flutter/fml/build_config.h" #include "flutter/fml/message_loop.h" @@ -23,11 +24,25 @@ namespace fml { -Thread::Thread(const std::string& name) : joined_(false) { +Thread::ThreadConfig::ThreadConfig(std::string name, ThreadPriority priority) + : thread_name_(name), thread_priority_(priority) {} + +void Thread::ThreadConfig::SetCurrentThreadName() { + Thread::SetCurrentThreadName(thread_name_); +} + +void Thread::ThreadConfig::SetCurrentThreadPriority() {} + +Thread::Thread(const std::string& name) + : Thread(ThreadConfig::DefaultConfigure(name)) {} + +Thread::Thread(std::unique_ptr configurer) + : thread_config_(std::move(configurer)), joined_(false) { fml::AutoResetWaitableEvent latch; fml::RefPtr runner; - thread_ = std::make_unique([&latch, &runner, name]() -> void { - SetCurrentThreadName(name); + thread_ = std::make_unique([&]() -> void { + thread_config_->SetCurrentThreadName(); + thread_config_->SetCurrentThreadPriority(); fml::MessageLoop::EnsureInitializedForCurrentThread(); auto& loop = MessageLoop::GetCurrent(); runner = loop.GetTaskRunner(); diff --git a/fml/thread.h b/fml/thread.h index 9d297c2a5502d..9f21e54571360 100644 --- a/fml/thread.h +++ b/fml/thread.h @@ -7,6 +7,7 @@ #include #include +#include #include #include "flutter/fml/macros.h" @@ -16,18 +17,65 @@ namespace fml { class Thread { public: - explicit Thread(const std::string& name = ""); + // Valid values for priority of Thread + enum class ThreadPriority : int { + // Suitable for threads that shouldn't disrupt high priority work. + BACKGROUND, + // Default priority level. + NORMAL, + // Suitable for threads which generate data for the display. + DISPLAY, + // Suitable for thread which raster data + RASTER, + }; + + /// The ThreadConfig is used for setting thread perorities + class ThreadConfig { + public: + explicit ThreadConfig(std::string name = "", + ThreadPriority priority = ThreadPriority::NORMAL); + + static std::unique_ptr DefaultConfigure( + const std::string& name = "") { + return std::make_unique(name); + } + + ThreadPriority thread_priority() const { return thread_priority_; } + + const std::string& thread_name() const { return thread_name_; } + + /// Set current thread name + virtual void SetCurrentThreadName(); + + /// default do nothing, which mean user can use platform api to set priority + /// example: iOS might use pthread_qos set thread priority, Android might + /// use ::setPriority set thread priority + virtual void SetCurrentThreadPriority(); + + virtual ~ThreadConfig() = default; + + private: + const std::string thread_name_; + ThreadPriority thread_priority_; + }; + + explicit Thread(const std::string& name); + + explicit Thread(std::unique_ptr configurer = + ThreadConfig::DefaultConfigure()); ~Thread(); fml::RefPtr GetTaskRunner() const; - void Join(); - static void SetCurrentThreadName(const std::string& name); + void Join(); + private: std::unique_ptr thread_; + /// ThreadConfigure is used for setting thread configure some like `priority` + std::unique_ptr thread_config_; fml::RefPtr task_runner_; std::atomic_bool joined_; diff --git a/fml/thread_unittests.cc b/fml/thread_unittests.cc index 5f8502753ce2d..e30a1435d09a8 100644 --- a/fml/thread_unittests.cc +++ b/fml/thread_unittests.cc @@ -4,6 +4,11 @@ #include "flutter/fml/thread.h" +#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID) +#include +#endif + +#include #include "gtest/gtest.h" TEST(Thread, CanStartAndEnd) { @@ -24,3 +29,78 @@ TEST(Thread, HasARunningMessageLoop) { thread.Join(); ASSERT_TRUE(done); } + +#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID) +TEST(Thread, ThreadNameCreatedWithConfig) { + const std::string name = "Thread1"; + fml::Thread thread(fml::Thread::ThreadConfig::DefaultConfigure(name)); + + bool done = false; + constexpr int NAMELEN = 8; + thread.GetTaskRunner()->PostTask([&done, &name]() { + done = true; + char thread_name[NAMELEN]; + pthread_t current_thread = pthread_self(); + pthread_getname_np(current_thread, thread_name, NAMELEN); + ASSERT_EQ(thread_name, name); + }); + thread.Join(); + ASSERT_TRUE(done); +} + +class MockThreadConfig : public fml::Thread::ThreadConfig { + public: + using fml::Thread::ThreadConfig::ThreadConfig; + + void SetCurrentThreadPriority() override { + pthread_t tid = pthread_self(); + struct sched_param param; + int policy = SCHED_OTHER; + switch (thread_priority()) { + case fml::Thread::ThreadPriority::DISPLAY: + param.sched_priority = 10; + break; + default: + param.sched_priority = 1; + } + pthread_setschedparam(tid, policy, ¶m); + } +}; + +TEST(Thread, ThreadPriorityCreatedWithConfig) { + const std::string thread1_name = "Thread1"; + const std::string thread2_name = "Thread2"; + fml::Thread thread(std::make_unique( + thread1_name, fml::Thread::ThreadPriority::NORMAL)); + + bool done = false; + constexpr int NAMELEN = 8; + struct sched_param param; + int policy; + thread.GetTaskRunner()->PostTask([&]() { + done = true; + char thread_name[NAMELEN]; + pthread_t current_thread = pthread_self(); + pthread_getname_np(current_thread, thread_name, NAMELEN); + pthread_getschedparam(current_thread, &policy, ¶m); + ASSERT_EQ(thread_name, thread1_name); + ASSERT_EQ(policy, SCHED_OTHER); + ASSERT_EQ(param.sched_priority, 1); + }); + + fml::Thread thread2(std::make_unique( + thread2_name, fml::Thread::ThreadPriority::DISPLAY)); + thread2.GetTaskRunner()->PostTask([&]() { + done = true; + char thread_name[NAMELEN]; + pthread_t current_thread = pthread_self(); + pthread_getname_np(current_thread, thread_name, NAMELEN); + pthread_getschedparam(current_thread, &policy, ¶m); + ASSERT_EQ(thread_name, thread2_name); + ASSERT_EQ(policy, SCHED_OTHER); + ASSERT_EQ(param.sched_priority, 10); + }); + thread.Join(); + ASSERT_TRUE(done); +} +#endif diff --git a/shell/common/thread_host.cc b/shell/common/thread_host.cc index 2e0d539fa82f0..278fe393d7cdd 100644 --- a/shell/common/thread_host.cc +++ b/shell/common/thread_host.cc @@ -4,32 +4,73 @@ #include "flutter/shell/common/thread_host.h" +#include +#include +#include +#include + namespace flutter { +std::string ThreadHost::ThreadName(Type type, std::string prefix) { + switch (type) { + case Type::Platform: + return prefix + ".platform"; + case Type::UI: + return prefix + ".ui"; + case Type::IO: + return prefix + ".io"; + case Type::RASTER: + return prefix + ".raster"; + case Type::Profiler: + return prefix + ".profiler"; + } +} + +std::unique_ptr ThreadHost::CreateThread( + Type type, + ThreadHost::ThreadConfig configure) { + std::string name = ThreadName(type, name_prefix); + if (configure != nullptr) { + return std::make_unique(std::move(configure)); + } + return std::make_unique( + fml::Thread::ThreadConfig::DefaultConfigure(name)); +} + ThreadHost::ThreadHost() = default; ThreadHost::ThreadHost(ThreadHost&&) = default; -ThreadHost::ThreadHost(std::string name_prefix_arg, uint64_t mask) +ThreadHost::ThreadHost(std::string name_prefix_arg, + uint64_t mask, + ThreadHostConfig configure_host) : name_prefix(name_prefix_arg) { if (mask & ThreadHost::Type::Platform) { - platform_thread = std::make_unique(name_prefix + ".platform"); + platform_thread = + CreateThread(ThreadHost::Type::Platform, + std::move(configure_host.platform_configure)); } if (mask & ThreadHost::Type::UI) { - ui_thread = std::make_unique(name_prefix + ".ui"); + ui_thread = CreateThread(ThreadHost::Type::UI, + std::move(configure_host.ui_configure)); } if (mask & ThreadHost::Type::RASTER) { - raster_thread = std::make_unique(name_prefix + ".raster"); + raster_thread = CreateThread(ThreadHost::Type::RASTER, + std::move(configure_host.raster_configure)); } if (mask & ThreadHost::Type::IO) { - io_thread = std::make_unique(name_prefix + ".io"); + io_thread = CreateThread(ThreadHost::Type::IO, + std::move(configure_host.io_configure)); } if (mask & ThreadHost::Type::Profiler) { - profiler_thread = std::make_unique(name_prefix + ".profiler"); + profiler_thread = + CreateThread(ThreadHost::Type::Profiler, + std::move(configure_host.profiler_configure)); + ; } } diff --git a/shell/common/thread_host.h b/shell/common/thread_host.h index d944963282cb9..25f0c169bf18f 100644 --- a/shell/common/thread_host.h +++ b/shell/common/thread_host.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_COMMON_THREAD_HOST_H_ #include +#include #include "flutter/fml/macros.h" #include "flutter/fml/thread.h" @@ -22,6 +23,19 @@ struct ThreadHost { Profiler = 1 << 4, }; + static std::string ThreadName(Type type, std::string prefix = ""); + + using ThreadConfig = std::unique_ptr; + /// The collection of all the thread configures, and we create custom thread + /// configure in engine to info the thread. + struct ThreadHostConfig { + ThreadConfig platform_configure; + ThreadConfig ui_configure; + ThreadConfig raster_configure; + ThreadConfig io_configure; + ThreadConfig profiler_configure; + }; + std::string name_prefix; std::unique_ptr platform_thread; std::unique_ptr ui_thread; @@ -35,9 +49,14 @@ struct ThreadHost { ThreadHost& operator=(ThreadHost&&) = default; - ThreadHost(std::string name_prefix, uint64_t type_mask); + ThreadHost(std::string name_prefix, + uint64_t type_mask, + ThreadHostConfig configure_host = ThreadHostConfig()); ~ThreadHost(); + + private: + std::unique_ptr CreateThread(Type type, ThreadConfig configure); }; } // namespace flutter diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index f425e2041cc1c..f802ce582f437 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -32,6 +32,47 @@ namespace flutter { +/// Inheriting ThreadConfigurer and use Android platform thread API to configure +/// the thread priorities +class PlatformAndroidThreadConfig : public fml::Thread::ThreadConfig { + public: + using fml::Thread::ThreadConfig::ThreadConfig; + + void SetCurrentThreadPriority() override { + switch (thread_priority()) { + case fml::Thread::ThreadPriority::BACKGROUND: { + if (::setpriority(PRIO_PROCESS, 0, 10) != 0) { + FML_LOG(ERROR) << "Failed to set IO task runner priority"; + } + break; + } + case fml::Thread::ThreadPriority::DISPLAY: { + if (::setpriority(PRIO_PROCESS, 0, -1) != 0) { + FML_LOG(ERROR) << "Failed to set UI task runner priority"; + } + break; + } + case fml::Thread::ThreadPriority::RASTER: { + // Android describes -8 as "most important display threads, for + // compositing the screen and retrieving input events". Conservatively + // set the raster thread to slightly lower priority than it. + if (::setpriority(PRIO_PROCESS, 0, -5) != 0) { + // Defensive fallback. Depending on the OEM, it may not be possible + // to set priority to -5. + if (::setpriority(PRIO_PROCESS, 0, -2) != 0) { + FML_LOG(ERROR) << "Failed to set raster task runner priority"; + } + } + break; + } + default: + if (::setpriority(PRIO_PROCESS, 0, 0) != 0) { + FML_LOG(ERROR) << "Failed to set priority"; + } + } + } +}; + static PlatformData GetDefaultPlatformData() { PlatformData platform_data; platform_data.lifecycle_state = "AppLifecycleState.detached"; @@ -45,10 +86,23 @@ AndroidShellHolder::AndroidShellHolder( static size_t thread_host_count = 1; auto thread_label = std::to_string(thread_host_count++); - thread_host_ = std::make_shared(); - *thread_host_ = {thread_label, ThreadHost::Type::UI | - ThreadHost::Type::RASTER | - ThreadHost::Type::IO}; + auto ui_thread_name = flutter::ThreadHost::ThreadName( + flutter::ThreadHost::Type::UI, thread_label); + auto raster_thread_name = flutter::ThreadHost::ThreadName( + flutter::ThreadHost::Type::RASTER, thread_label); + auto io_thread_name = flutter::ThreadHost::ThreadName( + flutter::ThreadHost::Type::IO, thread_label); + + thread_host_ = std::make_shared( + thread_label, + ThreadHost::Type::UI | ThreadHost::Type::RASTER | ThreadHost::Type::IO, + (flutter::ThreadHost::ThreadHostConfig){ + .ui_configure = std::make_unique( + ui_thread_name, fml::Thread::ThreadPriority::DISPLAY), + .raster_configure = std::make_unique( + raster_thread_name, fml::Thread::ThreadPriority::RASTER), + .io_configure = std::make_unique( + io_thread_name, fml::Thread::ThreadPriority::BACKGROUND)}); fml::WeakPtr weak_platform_view; Shell::CreateCallback on_create_platform_view = @@ -91,28 +145,6 @@ AndroidShellHolder::AndroidShellHolder( ui_runner, // ui io_runner // io ); - task_runners.GetRasterTaskRunner()->PostTask([]() { - // Android describes -8 as "most important display threads, for - // compositing the screen and retrieving input events". Conservatively - // set the raster thread to slightly lower priority than it. - if (::setpriority(PRIO_PROCESS, gettid(), -5) != 0) { - // Defensive fallback. Depending on the OEM, it may not be possible - // to set priority to -5. - if (::setpriority(PRIO_PROCESS, gettid(), -2) != 0) { - FML_LOG(ERROR) << "Failed to set raster task runner priority"; - } - } - }); - task_runners.GetUITaskRunner()->PostTask([]() { - if (::setpriority(PRIO_PROCESS, gettid(), -1) != 0) { - FML_LOG(ERROR) << "Failed to set UI task runner priority"; - } - }); - task_runners.GetIOTaskRunner()->PostTask([]() { - if (::setpriority(PRIO_PROCESS, gettid(), 1) != 0) { - FML_LOG(ERROR) << "Failed to set IO task runner priority"; - } - }); shell_ = Shell::Create(GetDefaultPlatformData(), // window data diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 03fe0a06e8e39..2142ee86a6b4b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -33,6 +33,38 @@ #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "flutter/shell/profiling/sampling_profiler.h" +/// Inheriting ThreadConfigurer and use iOS platform thread API to configure the thread priorities +class PlatformIOSThreadConfig : public fml::Thread::ThreadConfig { + public: + using fml::Thread::ThreadConfig::ThreadConfig; + + /// Using iOS platform thread API to configure thread priority + void SetCurrentThreadPriority() override { + switch (thread_priority()) { + case fml::Thread::ThreadPriority::BACKGROUND: { + [[NSThread currentThread] setThreadPriority:0]; + break; + } + case fml::Thread::ThreadPriority::NORMAL: { + [[NSThread currentThread] setThreadPriority:0.5]; + break; + } + case fml::Thread::ThreadPriority::RASTER: + case fml::Thread::ThreadPriority::DISPLAY: { + [[NSThread currentThread] setThreadPriority:1.0]; + sched_param param; + int policy; + pthread_t thread = pthread_self(); + if (!pthread_getschedparam(thread, &policy, ¶m)) { + param.sched_priority = 50; + pthread_setschedparam(thread, policy, ¶m); + } + break; + } + } + } +}; + NSString* const FlutterDefaultDartEntrypoint = nil; NSString* const FlutterDefaultInitialRoute = nil; NSString* const FlutterEngineWillDealloc = @"FlutterEngineWillDealloc"; @@ -605,18 +637,32 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { return [NSString stringWithFormat:@"%@.%zu", labelPrefix, ++s_shellCount]; } -+ (flutter::ThreadHost)makeThreadHost:(NSString*)threadLabel { ++ (std::shared_ptr)makeThreadHost:(NSString*)threadLabel { // The current thread will be used as the platform thread. Ensure that the message loop is // initialized. fml::MessageLoop::EnsureInitializedForCurrentThread(); uint32_t threadHostType = flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::RASTER | flutter::ThreadHost::Type::IO; + if ([FlutterEngine isProfilerEnabled]) { threadHostType = threadHostType | flutter::ThreadHost::Type::Profiler; } - return {threadLabel.UTF8String, // label - threadHostType}; + auto ui_thread_name = + flutter::ThreadHost::ThreadName(flutter::ThreadHost::Type::UI, threadLabel.UTF8String); + auto raster_thread_name = + flutter::ThreadHost::ThreadName(flutter::ThreadHost::Type::RASTER, threadLabel.UTF8String); + auto io_thread_name = + flutter::ThreadHost::ThreadName(flutter::ThreadHost::Type::IO, threadLabel.UTF8String); + return std::make_shared( + threadLabel.UTF8String, threadHostType, + (flutter::ThreadHost::ThreadHostConfig){ + .ui_configure = std::make_unique( + ui_thread_name, fml::Thread::ThreadPriority::DISPLAY), + .raster_configure = std::make_unique( + raster_thread_name, fml::Thread::ThreadPriority::RASTER), + .io_configure = std::make_unique( + io_thread_name, fml::Thread::ThreadPriority::BACKGROUND)}); } static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) { @@ -651,8 +697,7 @@ - (BOOL)createShell:(NSString*)entrypoint SetEntryPoint(&settings, entrypoint, libraryURI); NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; - _threadHost = std::make_shared(); - *_threadHost = [FlutterEngine makeThreadHost:threadLabel]; + _threadHost = [FlutterEngine makeThreadHost:threadLabel]; // Lambda captures by pointers to ObjC objects are fine here because the // create call is synchronous.