| // Copyright 2016 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/task/thread_pool/thread_pool_impl.h" | |
| #include <stddef.h> | |
| #include <memory> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "base/cfi_buildflags.h" | |
| #include "base/containers/span.h" | |
| #include "base/debug/stack_trace.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/message_loop/message_pump_type.h" | |
| #include "base/metrics/field_trial.h" | |
| #include "base/metrics/field_trial_params.h" | |
| #include "base/system/sys_info.h" | |
| #include "base/task/task_features.h" | |
| #include "base/task/task_traits.h" | |
| #include "base/task/thread_pool/environment_config.h" | |
| #include "base/task/thread_pool/test_task_factory.h" | |
| #include "base/task/thread_pool/test_utils.h" | |
| #include "base/task/thread_pool/worker_thread_observer.h" | |
| #include "base/task/updateable_sequenced_task_runner.h" | |
| #include "base/test/bind.h" | |
| #include "base/test/gtest_util.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "base/test/test_timeouts.h" | |
| #include "base/test/test_waitable_event.h" | |
| #include "base/threading/platform_thread.h" | |
| #include "base/threading/sequence_local_storage_slot.h" | |
| #include "base/threading/simple_thread.h" | |
| #include "base/threading/thread.h" | |
| #include "base/threading/thread_restrictions.h" | |
| #include "base/time/time.h" | |
| #include "build/build_config.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #if BUILDFLAG(IS_POSIX) | |
| #include <unistd.h> | |
| #include "base/debug/leak_annotations.h" | |
| #include "base/files/file_descriptor_watcher_posix.h" | |
| #include "base/files/file_util.h" | |
| #include "base/posix/eintr_wrapper.h" | |
| #endif // BUILDFLAG(IS_POSIX) | |
| #if BUILDFLAG(IS_WIN) | |
| #include "base/win/com_init_util.h" | |
| #endif // BUILDFLAG(IS_WIN) | |
| namespace base::internal { | |
| namespace { | |
| constexpr size_t kMaxNumForegroundThreads = 4; | |
| constexpr size_t kMaxNumUtilityThreads = 2; | |
| constexpr size_t kMaxNumAudioThreads = 2; | |
| struct TraitsExecutionModePair { | |
| TraitsExecutionModePair(const TaskTraits& traits, | |
| TaskSourceExecutionMode execution_mode) | |
| : traits(traits), execution_mode(execution_mode) {} | |
| TaskTraits traits; | |
| TaskSourceExecutionMode execution_mode; | |
| }; | |
| // Returns true if a task with |traits| could run at background thread priority | |
| // on this platform. Even if this returns true, it is possible that the task | |
| // won't run at background thread priority if a native thread group is used. | |
| bool TraitsSupportBackgroundThreadType(const TaskTraits& traits, | |
| ThreadType originating_thread_type, | |
| bool inherit_by_default) { | |
| if (traits.thread_policy() != ThreadPolicy::PREFER_BACKGROUND || | |
| !CanUseBackgroundThreadTypeForWorkerThread()) { | |
| return false; | |
| } | |
| if (traits.inherit_thread_type()) { | |
| return std::min(traits.max_thread_type(), originating_thread_type) == | |
| ThreadType::kBackground; | |
| } | |
| if (inherit_by_default && !traits.priority_set_explicitly()) { | |
| return std::min(ThreadType::kDefault, originating_thread_type) == | |
| ThreadType::kBackground; | |
| } | |
| return traits.priority() == TaskPriority::BEST_EFFORT; | |
| } | |
| // Returns true if a task with |traits| could run at utility thread | |
| // type on this platform. Even if this returns true, it is possible that the | |
| // task won't run at efficient thread priority if a native thread group is used | |
| // or the utility thread group is disabled. | |
| bool TraitsSupportUtilityThreadType(const TaskTraits& traits, | |
| ThreadType originating_thread_type, | |
| bool inherit_by_default) { | |
| if (traits.thread_policy() != ThreadPolicy::PREFER_BACKGROUND || | |
| !CanUseUtilityThreadTypeForWorkerThread()) { | |
| return false; | |
| } | |
| if (traits.inherit_thread_type()) { | |
| return std::min(traits.max_thread_type(), originating_thread_type) <= | |
| ThreadType::kUtility; | |
| } | |
| if (inherit_by_default && !traits.priority_set_explicitly()) { | |
| return std::min(ThreadType::kDefault, originating_thread_type) <= | |
| ThreadType::kUtility; | |
| } | |
| return traits.priority() <= TaskPriority::USER_VISIBLE; | |
| } | |
| // Verify that the current thread type and I/O restrictions are appropriate to | |
| // run a Task with |traits|. | |
| // Note: ExecutionMode is verified inside TestTaskFactory. | |
| void VerifyTaskEnvironment(const TaskTraits& traits, | |
| bool use_resource_efficient_group, | |
| bool use_high_priority_group, | |
| ThreadType originating_thread_type, | |
| bool inherit_by_default) { | |
| const std::string thread_name(PlatformThread::GetName()); | |
| const bool is_single_threaded = | |
| (thread_name.find("SingleThread") != std::string::npos); | |
| ThreadType expected_thread_type = ThreadType::kDefault; | |
| if (TraitsSupportBackgroundThreadType(traits, originating_thread_type, | |
| inherit_by_default)) { | |
| expected_thread_type = ThreadType::kBackground; | |
| } else if (use_resource_efficient_group && | |
| TraitsSupportUtilityThreadType(traits, originating_thread_type, | |
| inherit_by_default)) { | |
| expected_thread_type = ThreadType::kUtility; | |
| } else if (!is_single_threaded && use_high_priority_group && | |
| traits.inherit_thread_type() && | |
| std::min(traits.max_thread_type(), originating_thread_type) >= | |
| ThreadType::kPresentation) { | |
| expected_thread_type = | |
| std::min(traits.max_thread_type(), originating_thread_type); | |
| } | |
| EXPECT_EQ(expected_thread_type, PlatformThread::GetCurrentThreadType()); | |
| if (traits.may_block()) { | |
| AssertBlockingAllowed(); | |
| } else { | |
| AssertBlockingDisallowedForTesting(); | |
| } | |
| // Verify that the thread the task is running on is named as expected. | |
| EXPECT_THAT(thread_name, ::testing::HasSubstr("ThreadPool")); | |
| EXPECT_THAT(thread_name, ::testing::HasSubstr([&]() { | |
| switch (expected_thread_type) { | |
| case ThreadType::kBackground: | |
| return "Background"; | |
| case ThreadType::kUtility: | |
| return "Utility"; | |
| case ThreadType::kDefault: | |
| return "Foreground"; | |
| case ThreadType::kPresentation: | |
| return "Presentation"; | |
| case ThreadType::kAudioProcessing: | |
| return "Audio"; | |
| default: | |
| NOTREACHED(); | |
| } | |
| }())); | |
| if (is_single_threaded) { | |
| // SingleThread workers discriminate blocking/non-blocking tasks. | |
| if (traits.may_block()) { | |
| EXPECT_THAT(thread_name, ::testing::HasSubstr("Blocking")); | |
| } else { | |
| EXPECT_THAT(thread_name, | |
| ::testing::Not(::testing::HasSubstr("Blocking"))); | |
| } | |
| } else { | |
| EXPECT_THAT(thread_name, ::testing::Not(::testing::HasSubstr("Blocking"))); | |
| } | |
| } | |
| void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits, | |
| bool use_resource_efficient_group, | |
| bool use_high_priority_group, | |
| ThreadType originating_thread_type, | |
| TestWaitableEvent* event, | |
| bool inherit_by_default) { | |
| DCHECK(event); | |
| VerifyTaskEnvironment(traits, use_resource_efficient_group, | |
| use_high_priority_group, originating_thread_type, | |
| inherit_by_default); | |
| event->Signal(); | |
| } | |
| void VerifyTimeAndTaskEnvironmentAndSignalEvent( | |
| const TaskTraits& traits, | |
| bool use_resource_efficient_group, | |
| ThreadType originating_thread_type, | |
| TimeTicks expected_time, | |
| TestWaitableEvent* event) { | |
| DCHECK(event); | |
| EXPECT_LE(expected_time, TimeTicks::Now()); | |
| VerifyTaskEnvironment(traits, use_resource_efficient_group, false, | |
| originating_thread_type, | |
| /*inherit_by_default=*/false); | |
| event->Signal(); | |
| } | |
| void VerifyOrderAndTaskEnvironmentAndSignalEvent( | |
| const TaskTraits& traits, | |
| bool use_resource_efficient_group, | |
| ThreadType originating_thread_type, | |
| TestWaitableEvent* expected_previous_event, | |
| TestWaitableEvent* event) { | |
| DCHECK(event); | |
| if (expected_previous_event) { | |
| EXPECT_TRUE(expected_previous_event->IsSignaled()); | |
| } | |
| VerifyTaskEnvironment(traits, use_resource_efficient_group, | |
| /*use_high_priority_group=*/false, | |
| originating_thread_type, | |
| /*inherit_by_default=*/false); | |
| event->Signal(); | |
| } | |
| scoped_refptr<TaskRunner> CreateTaskRunnerAndExecutionMode( | |
| ThreadPoolImpl* thread_pool, | |
| const TaskTraits& traits, | |
| TaskSourceExecutionMode execution_mode, | |
| SingleThreadTaskRunnerThreadMode default_single_thread_task_runner_mode = | |
| SingleThreadTaskRunnerThreadMode::SHARED) { | |
| switch (execution_mode) { | |
| case TaskSourceExecutionMode::kParallel: | |
| return thread_pool->CreateTaskRunner(traits); | |
| case TaskSourceExecutionMode::kSequenced: | |
| return thread_pool->CreateSequencedTaskRunner(traits); | |
| case TaskSourceExecutionMode::kSingleThread: { | |
| return thread_pool->CreateSingleThreadTaskRunner( | |
| traits, default_single_thread_task_runner_mode); | |
| } | |
| case TaskSourceExecutionMode::kJob: | |
| break; | |
| } | |
| ADD_FAILURE() << "Unknown ExecutionMode"; | |
| return nullptr; | |
| } | |
| class ThreadPostingTasks : public SimpleThread { | |
| public: | |
| // Creates a thread that posts Tasks to |thread_pool| with |traits| and | |
| // |execution_mode|. | |
| ThreadPostingTasks(ThreadPoolImpl* thread_pool, | |
| const TaskTraits& traits, | |
| bool use_resource_efficient_group, | |
| bool use_high_priority_group, | |
| bool inherit_by_default, | |
| TaskSourceExecutionMode execution_mode) | |
| : SimpleThread("ThreadPostingTasks"), | |
| traits_(traits), | |
| use_resource_efficient_group_(use_resource_efficient_group), | |
| use_high_priority_group_(use_high_priority_group), | |
| inherit_by_default_(inherit_by_default), | |
| originating_thread_type_(GetCurrentTaskImportance()), | |
| factory_(CreateTaskRunnerAndExecutionMode(thread_pool, | |
| traits, | |
| execution_mode), | |
| execution_mode) {} | |
| ThreadPostingTasks(const ThreadPostingTasks&) = delete; | |
| ThreadPostingTasks& operator=(const ThreadPostingTasks&) = delete; | |
| void WaitForAllTasksToRun() { factory_.WaitForAllTasksToRun(); } | |
| private: | |
| void Run() override { | |
| // Match `originating_thread_type_` so that parallel tasks are posted with | |
| // the same priority as sequenced tasks. | |
| PlatformThread::RaiseThreadTypeLease lease(originating_thread_type_); | |
| ASSERT_EQ(originating_thread_type_, GetCurrentTaskImportance()); | |
| const size_t kNumTasksPerThread = 150; | |
| for (size_t i = 0; i < kNumTasksPerThread; ++i) { | |
| factory_.PostTask( | |
| test::TestTaskFactory::PostNestedTask::NO, | |
| BindOnce(&VerifyTaskEnvironment, traits_, | |
| use_resource_efficient_group_, use_high_priority_group_, | |
| originating_thread_type_, inherit_by_default_)); | |
| } | |
| } | |
| const TaskTraits traits_; | |
| bool use_resource_efficient_group_; | |
| bool use_high_priority_group_; | |
| bool inherit_by_default_; | |
| ThreadType originating_thread_type_; | |
| test::TestTaskFactory factory_; | |
| }; | |
| // Returns a vector with a TraitsExecutionModePair for each valid combination of | |
| // {ExecutionMode, TaskPriority, ThreadPolicy, MayBlock()}. | |
| std::vector<TraitsExecutionModePair> GetTraitsExecutionModePairs() { | |
| std::vector<TraitsExecutionModePair> params; | |
| constexpr TaskSourceExecutionMode execution_modes[] = { | |
| TaskSourceExecutionMode::kParallel, TaskSourceExecutionMode::kSequenced, | |
| TaskSourceExecutionMode::kSingleThread}; | |
| constexpr ThreadPolicy thread_policies[] = { | |
| ThreadPolicy::PREFER_BACKGROUND, ThreadPolicy::MUST_USE_FOREGROUND}; | |
| for (TaskSourceExecutionMode execution_mode : execution_modes) { | |
| for (ThreadPolicy thread_policy : thread_policies) { | |
| for (size_t priority_index = static_cast<size_t>(TaskPriority::LOWEST); | |
| priority_index <= static_cast<size_t>(TaskPriority::HIGHEST); | |
| ++priority_index) { | |
| const TaskPriority priority = static_cast<TaskPriority>(priority_index); | |
| params.push_back( | |
| TraitsExecutionModePair({priority, thread_policy}, execution_mode)); | |
| params.push_back(TraitsExecutionModePair( | |
| {priority, thread_policy, MayBlock()}, execution_mode)); | |
| } | |
| } | |
| params.push_back( | |
| TraitsExecutionModePair({InheritThreadType()}, execution_mode)); | |
| params.push_back(TraitsExecutionModePair( | |
| {InheritThreadType(), MaxThreadType(ThreadType::kPresentation)}, | |
| execution_mode)); | |
| params.push_back(TraitsExecutionModePair( | |
| {InheritThreadType(), MaxThreadType(ThreadType::kDefault)}, | |
| execution_mode)); | |
| params.push_back(TraitsExecutionModePair( | |
| {InheritThreadType(), MaxThreadType(ThreadType::kBackground)}, | |
| execution_mode)); | |
| } | |
| return params; | |
| } | |
| // Returns a vector with enough TraitsExecutionModePairs to cover all valid | |
| // combinations of task destination (background/foreground ThreadGroup, | |
| // single-thread) and whether the task is affected by a BEST_EFFORT fence. | |
| std::vector<TraitsExecutionModePair> | |
| GetTraitsExecutionModePairsToCoverAllSchedulingOptions() { | |
| return {TraitsExecutionModePair({TaskPriority::BEST_EFFORT}, | |
| TaskSourceExecutionMode::kSequenced), | |
| TraitsExecutionModePair({TaskPriority::USER_VISIBLE}, | |
| TaskSourceExecutionMode::kSequenced), | |
| TraitsExecutionModePair({TaskPriority::USER_BLOCKING}, | |
| TaskSourceExecutionMode::kSequenced), | |
| TraitsExecutionModePair({TaskPriority::BEST_EFFORT}, | |
| TaskSourceExecutionMode::kSingleThread), | |
| TraitsExecutionModePair({TaskPriority::USER_VISIBLE}, | |
| TaskSourceExecutionMode::kSingleThread), | |
| TraitsExecutionModePair({TaskPriority::USER_BLOCKING}, | |
| TaskSourceExecutionMode::kSingleThread)}; | |
| } | |
| class ThreadPoolImplTestBase : public testing::Test { | |
| public: | |
| ThreadPoolImplTestBase() | |
| : thread_pool_(std::make_unique<ThreadPoolImpl>("Test")), | |
| service_thread_("ServiceThread") { | |
| Thread::Options service_thread_options; | |
| service_thread_options.message_pump_type = MessagePumpType::IO; | |
| service_thread_.StartWithOptions(std::move(service_thread_options)); | |
| } | |
| ThreadPoolImplTestBase(const ThreadPoolImplTestBase&) = delete; | |
| ThreadPoolImplTestBase& operator=(const ThreadPoolImplTestBase&) = delete; | |
| virtual bool GetUseResourceEfficientThreadGroup() const = 0; | |
| virtual bool GetUseHighPriorityThreadGroup() const = 0; | |
| virtual bool GetInheritTaskImportanceByDefault() const { return false; } | |
| void set_worker_thread_observer( | |
| std::unique_ptr<WorkerThreadObserver> worker_thread_observer) { | |
| worker_thread_observer_ = std::move(worker_thread_observer); | |
| } | |
| void StartThreadPool( | |
| size_t max_num_foreground_threads = kMaxNumForegroundThreads, | |
| size_t max_num_utility_threads = kMaxNumUtilityThreads, | |
| size_t max_num_audio_threads = kMaxNumAudioThreads, | |
| TimeDelta reclaim_time = Seconds(30)) { | |
| SetupFeatures(); | |
| ThreadPoolInstance::InitParams init_params(max_num_foreground_threads, | |
| max_num_utility_threads, | |
| max_num_audio_threads); | |
| init_params.suggested_reclaim_time = reclaim_time; | |
| thread_pool_->Start(init_params, worker_thread_observer_.get()); | |
| } | |
| void TearDown() override { | |
| if (did_tear_down_) { | |
| return; | |
| } | |
| if (thread_pool_) { | |
| thread_pool_->FlushForTesting(); | |
| thread_pool_->JoinForTesting(); | |
| thread_pool_.reset(); | |
| } | |
| feature_list_.Reset(); | |
| base::internal::JobTaskSource::InitializeFeatures(); | |
| did_tear_down_ = true; | |
| } | |
| base::test::ScopedFeatureList feature_list_; | |
| std::unique_ptr<ThreadPoolImpl> thread_pool_; | |
| Thread service_thread_; | |
| private: | |
| void SetupFeatures() { | |
| std::vector<base::test::FeatureRef> enabled_features; | |
| std::vector<base::test::FeatureRef> disabled_features; | |
| if (GetUseResourceEfficientThreadGroup()) { | |
| enabled_features.push_back(kUseUtilityThreadGroup); | |
| } else { | |
| disabled_features.push_back(kUseUtilityThreadGroup); | |
| } | |
| if (GetUseHighPriorityThreadGroup()) { | |
| enabled_features.push_back(kUseHighPriorityThreadGroup); | |
| } else { | |
| disabled_features.push_back(kUseHighPriorityThreadGroup); | |
| } | |
| if (GetInheritTaskImportanceByDefault()) { | |
| enabled_features.push_back(kInheritTaskImportanceByDefault); | |
| } else { | |
| disabled_features.push_back(kInheritTaskImportanceByDefault); | |
| } | |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); | |
| base::internal::JobTaskSource::InitializeFeatures(); | |
| } | |
| std::unique_ptr<WorkerThreadObserver> worker_thread_observer_; | |
| bool did_tear_down_ = false; | |
| }; | |
| class ThreadPoolImplTest | |
| : public ThreadPoolImplTestBase, | |
| public testing::WithParamInterface< | |
| std::tuple<bool /* use_resource_efficient_thread_group */, | |
| bool /* use_high_priority_thread_group */, | |
| bool /* inherit_task_importance_by_default */>> { | |
| public: | |
| bool GetUseResourceEfficientThreadGroup() const override { | |
| return std::get<0>(GetParam()); | |
| } | |
| bool GetUseHighPriorityThreadGroup() const override { | |
| return std::get<1>(GetParam()); | |
| } | |
| bool GetInheritTaskImportanceByDefault() const override { | |
| return std::get<2>(GetParam()); | |
| } | |
| }; | |
| // Tests run for enough traits and execution mode combinations to cover all | |
| // valid combinations of task destination (background/foreground ThreadGroup, | |
| // single-thread) and whether the task is affected by a BEST_EFFORT fence. | |
| class ThreadPoolImplTest_CoverAllSchedulingOptions | |
| : public ThreadPoolImplTestBase, | |
| public testing::WithParamInterface< | |
| std::tuple<bool /* use_resource_efficient_thread_group */, | |
| TraitsExecutionModePair>> { | |
| public: | |
| ThreadPoolImplTest_CoverAllSchedulingOptions() = default; | |
| ThreadPoolImplTest_CoverAllSchedulingOptions( | |
| const ThreadPoolImplTest_CoverAllSchedulingOptions&) = delete; | |
| ThreadPoolImplTest_CoverAllSchedulingOptions& operator=( | |
| const ThreadPoolImplTest_CoverAllSchedulingOptions&) = delete; | |
| bool GetUseResourceEfficientThreadGroup() const override { | |
| return std::get<0>(GetParam()); | |
| } | |
| bool GetUseHighPriorityThreadGroup() const override { return false; } | |
| TaskTraits GetTraits() const { return std::get<1>(GetParam()).traits; } | |
| TaskSourceExecutionMode GetExecutionMode() const { | |
| return std::get<1>(GetParam()).execution_mode; | |
| } | |
| }; | |
| } // namespace | |
| // Verifies that a Task posted via PostDelayedTask with parameterized TaskTraits | |
| // and no delay runs on a thread with the expected priority and I/O | |
| // restrictions. The ExecutionMode parameter is ignored by this test. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, PostDelayedTaskNoDelay) { | |
| StartThreadPool(); | |
| TestWaitableEvent task_ran; | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, GetTraits(), | |
| BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), | |
| GetUseHighPriorityThreadGroup(), ThreadType::kDefault, | |
| Unretained(&task_ran), | |
| /*inherit_by_default=*/false), | |
| TimeDelta()); | |
| task_ran.Wait(); | |
| } | |
| // Verifies that a Task posted via PostDelayedTask with parameterized | |
| // TaskTraits and a non-zero delay runs on a thread with the expected priority | |
| // and I/O restrictions after the delay expires. The ExecutionMode parameter is | |
| // ignored by this test. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, PostDelayedTaskWithDelay) { | |
| StartThreadPool(); | |
| TestWaitableEvent task_ran; | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, GetTraits(), | |
| BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), ThreadType::kDefault, | |
| TimeTicks::Now() + TestTimeouts::tiny_timeout(), | |
| Unretained(&task_ran)), | |
| TestTimeouts::tiny_timeout()); | |
| task_ran.Wait(); | |
| } | |
| namespace { | |
| scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunnerAndExecutionMode( | |
| ThreadPoolImpl* thread_pool, | |
| const TaskTraits& traits, | |
| TaskSourceExecutionMode execution_mode, | |
| SingleThreadTaskRunnerThreadMode default_single_thread_task_runner_mode = | |
| SingleThreadTaskRunnerThreadMode::SHARED) { | |
| switch (execution_mode) { | |
| case TaskSourceExecutionMode::kSequenced: | |
| return thread_pool->CreateSequencedTaskRunner(traits); | |
| case TaskSourceExecutionMode::kSingleThread: { | |
| return thread_pool->CreateSingleThreadTaskRunner( | |
| traits, default_single_thread_task_runner_mode); | |
| } | |
| case TaskSourceExecutionMode::kParallel: | |
| case TaskSourceExecutionMode::kJob: | |
| ADD_FAILURE() << "Tests below don't cover these modes"; | |
| return nullptr; | |
| } | |
| ADD_FAILURE() << "Unknown ExecutionMode"; | |
| return nullptr; | |
| } | |
| } // namespace | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| PostDelayedTaskAtViaTaskRunner) { | |
| StartThreadPool(); | |
| TestWaitableEvent task_ran; | |
| // Only runs for kSequenced and kSingleThread. | |
| auto handle = | |
| CreateSequencedTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostCancelableDelayedTaskAt( | |
| subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE, | |
| BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), | |
| ThreadType::kDefault, | |
| TimeTicks::Now() + TestTimeouts::tiny_timeout(), | |
| Unretained(&task_ran)), | |
| TimeTicks::Now() + TestTimeouts::tiny_timeout(), | |
| subtle::DelayPolicy::kFlexibleNoSooner); | |
| task_ran.Wait(); | |
| } | |
| // Verifies that Tasks posted via a TaskRunner with parameterized TaskTraits and | |
| // ExecutionMode run on a thread with the expected priority and I/O restrictions | |
| // and respect the characteristics of their ExecutionMode. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, PostTasksViaTaskRunner) { | |
| StartThreadPool(); | |
| test::TestTaskFactory factory( | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()), | |
| GetExecutionMode()); | |
| const size_t kNumTasksPerTest = 150; | |
| for (size_t i = 0; i < kNumTasksPerTest; ++i) { | |
| factory.PostTask( | |
| test::TestTaskFactory::PostNestedTask::NO, | |
| BindOnce(&VerifyTaskEnvironment, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), | |
| GetUseHighPriorityThreadGroup(), ThreadType::kDefault, | |
| /*inherit_by_default=*/false)); | |
| } | |
| factory.WaitForAllTasksToRun(); | |
| } | |
| // Verifies that a task posted via PostDelayedTask without a delay doesn't run | |
| // before Start() is called. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| PostDelayedTaskNoDelayBeforeStart) { | |
| TestWaitableEvent task_running; | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, GetTraits(), | |
| BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), | |
| GetUseHighPriorityThreadGroup(), ThreadType::kDefault, | |
| Unretained(&task_running), | |
| /*inherit_by_default=*/false), | |
| TimeDelta()); | |
| // Wait a little bit to make sure that the task doesn't run before Start(). | |
| // Note: This test won't catch a case where the task runs just after the check | |
| // and before Start(). However, we expect the test to be flaky if the tested | |
| // code allows that to happen. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(task_running.IsSignaled()); | |
| StartThreadPool(); | |
| task_running.Wait(); | |
| } | |
| // Verifies that a task posted via PostDelayedTask with a delay doesn't run | |
| // before Start() is called. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| PostDelayedTaskWithDelayBeforeStart) { | |
| TestWaitableEvent task_running; | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, GetTraits(), | |
| BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetTraits(), | |
| GetUseResourceEfficientThreadGroup(), ThreadType::kDefault, | |
| TimeTicks::Now() + TestTimeouts::tiny_timeout(), | |
| Unretained(&task_running)), | |
| TestTimeouts::tiny_timeout()); | |
| // Wait a little bit to make sure that the task doesn't run before Start(). | |
| // Note: This test won't catch a case where the task runs just after the check | |
| // and before Start(). However, we expect the test to be flaky if the tested | |
| // code allows that to happen. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(task_running.IsSignaled()); | |
| StartThreadPool(); | |
| task_running.Wait(); | |
| } | |
| // Verifies that a task posted via a TaskRunner doesn't run before Start() is | |
| // called. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| PostTaskViaTaskRunnerBeforeStart) { | |
| bool use_resource_efficient_thread_group = | |
| GetUseResourceEfficientThreadGroup(); | |
| // The worker_thread of SingleThreadTaskRunner is selected before | |
| // kUseUtilityThreadGroup feature is set up at StartThreadPool(). | |
| if (GetExecutionMode() == TaskSourceExecutionMode::kSingleThread) { | |
| use_resource_efficient_thread_group = false; | |
| } | |
| TestWaitableEvent task_running; | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, | |
| BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetTraits(), | |
| use_resource_efficient_thread_group, | |
| GetUseHighPriorityThreadGroup(), ThreadType::kDefault, | |
| Unretained(&task_running), | |
| /*inherit_by_default=*/false)); | |
| // Wait a little bit to make sure that the task doesn't run before Start(). | |
| // Note: This test won't catch a case where the task runs just after the check | |
| // and before Start(). However, we expect the test to be flaky if the tested | |
| // code allows that to happen. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(task_running.IsSignaled()); | |
| StartThreadPool(); | |
| // This should not hang if the task runs after Start(). | |
| task_running.Wait(); | |
| } | |
| // Verify that posting tasks after the thread pool was destroyed fails but | |
| // doesn't crash. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, PostTaskAfterDestroy) { | |
| StartThreadPool(); | |
| auto task_runner = CreateTaskRunnerAndExecutionMode( | |
| thread_pool_.get(), GetTraits(), GetExecutionMode()); | |
| EXPECT_TRUE(task_runner->PostTask(FROM_HERE, DoNothing())); | |
| thread_pool_->JoinForTesting(); | |
| thread_pool_.reset(); | |
| EXPECT_FALSE( | |
| task_runner->PostTask(FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE))); | |
| } | |
| // Verifies that FlushAsyncForTesting() calls back correctly for all trait and | |
| // execution mode pairs. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| FlushAsyncForTestingSimple) { | |
| StartThreadPool(); | |
| TestWaitableEvent unblock_task; | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode(), | |
| SingleThreadTaskRunnerThreadMode::DEDICATED) | |
| ->PostTask(FROM_HERE, unblock_task.GetWaitCallbackForTesting()); | |
| TestWaitableEvent flush_event; | |
| thread_pool_->FlushAsyncForTesting( | |
| BindOnce(&TestWaitableEvent::Signal, Unretained(&flush_event))); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(flush_event.IsSignaled()); | |
| unblock_task.Signal(); | |
| flush_event.Wait(); | |
| } | |
| // Verifies that tasks only run when allowed by fences. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, Fence) { | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| thread_pool_->BeginFence(); | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| can_run.Set(); | |
| thread_pool_->EndFence(); | |
| did_run.Wait(); | |
| } | |
| // Verifies that multiple fences can exist at the same time. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, MultipleFences) { | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| thread_pool_->BeginFence(); | |
| thread_pool_->BeginFence(); | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| thread_pool_->EndFence(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| // The task can only run when both fences are removed. | |
| can_run.Set(); | |
| thread_pool_->EndFence(); | |
| did_run.Wait(); | |
| } | |
| // Verifies that a call to BeginFence() before Start() is honored. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, FenceBeforeStart) { | |
| thread_pool_->BeginFence(); | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| can_run.Set(); | |
| thread_pool_->EndFence(); | |
| did_run.Wait(); | |
| } | |
| // Verifies that tasks only run when allowed by BEST_EFFORT fences. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, BestEffortFence) { | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| thread_pool_->BeginBestEffortFence(); | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| if (GetTraits().priority() == TaskPriority::BEST_EFFORT) { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| } | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| can_run.Set(); | |
| thread_pool_->EndBestEffortFence(); | |
| did_run.Wait(); | |
| } | |
| // Verifies that multiple BEST_EFFORT fences can exist at the same time. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, MultipleBestEffortFences) { | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| thread_pool_->BeginBestEffortFence(); | |
| thread_pool_->BeginBestEffortFence(); | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| if (GetTraits().priority() == TaskPriority::BEST_EFFORT) { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| } | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| thread_pool_->EndBestEffortFence(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| // The task can only run when both fences are removed. | |
| can_run.Set(); | |
| thread_pool_->EndBestEffortFence(); | |
| did_run.Wait(); | |
| } | |
| // Verifies that a call to BeginBestEffortFence() before Start() is honored. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| BestEffortFenceBeforeStart) { | |
| thread_pool_->BeginBestEffortFence(); | |
| StartThreadPool(); | |
| AtomicFlag can_run; | |
| TestWaitableEvent did_run; | |
| CreateTaskRunnerAndExecutionMode(thread_pool_.get(), GetTraits(), | |
| GetExecutionMode()) | |
| ->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| if (GetTraits().priority() == TaskPriority::BEST_EFFORT) { | |
| EXPECT_TRUE(can_run.IsSet()); | |
| } | |
| did_run.Signal(); | |
| })); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| can_run.Set(); | |
| thread_pool_->EndBestEffortFence(); | |
| did_run.Wait(); | |
| } | |
| // Spawns threads that simultaneously post Tasks to TaskRunners with various | |
| // TaskTraits and ExecutionModes. Verifies that each Task runs on a thread with | |
| // the expected priority and I/O restrictions and respects the characteristics | |
| // of its ExecutionMode. | |
| TEST_P(ThreadPoolImplTest, MultipleTraitsExecutionModePair) { | |
| StartThreadPool(); | |
| // Ensure originating ThreadType is kAudioProcessing to allow posting to high | |
| // priority thread groups. | |
| PlatformThread::RaiseThreadTypeLease lease(ThreadType::kAudioProcessing); | |
| ASSERT_EQ(ThreadType::kAudioProcessing, GetCurrentTaskImportance()); | |
| std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks; | |
| for (const auto& test_params : GetTraitsExecutionModePairs()) { | |
| threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>( | |
| thread_pool_.get(), test_params.traits, | |
| GetUseResourceEfficientThreadGroup(), GetUseHighPriorityThreadGroup(), | |
| GetInheritTaskImportanceByDefault(), test_params.execution_mode)); | |
| threads_posting_tasks.back()->Start(); | |
| } | |
| for (const auto& thread : threads_posting_tasks) { | |
| thread->WaitForAllTasksToRun(); | |
| thread->Join(); | |
| } | |
| } | |
| TEST_P(ThreadPoolImplTest, GetMaxConcurrentForegroundTasks) { | |
| StartThreadPool(); | |
| EXPECT_EQ(kMaxNumForegroundThreads, | |
| thread_pool_->GetMaxConcurrentForegroundTasks()); | |
| } | |
| // Verify that the RunsTasksInCurrentSequence() method of a SequencedTaskRunner | |
| // returns false when called from a task that isn't part of the sequence. | |
| TEST_P(ThreadPoolImplTest, SequencedRunsTasksInCurrentSequence) { | |
| StartThreadPool(); | |
| auto single_thread_task_runner = thread_pool_->CreateSingleThreadTaskRunner( | |
| {}, SingleThreadTaskRunnerThreadMode::SHARED); | |
| auto sequenced_task_runner = thread_pool_->CreateSequencedTaskRunner({}); | |
| TestWaitableEvent task_ran; | |
| single_thread_task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce( | |
| [](scoped_refptr<SequencedTaskRunner> sequenced_task_runner, | |
| TestWaitableEvent* task_ran) { | |
| EXPECT_FALSE(sequenced_task_runner->RunsTasksInCurrentSequence()); | |
| task_ran->Signal(); | |
| }, | |
| sequenced_task_runner, Unretained(&task_ran))); | |
| task_ran.Wait(); | |
| } | |
| // Verify that the RunsTasksInCurrentSequence() method of a | |
| // SingleThreadTaskRunner returns false when called from a task that isn't part | |
| // of the sequence. | |
| TEST_P(ThreadPoolImplTest, SingleThreadRunsTasksInCurrentSequence) { | |
| StartThreadPool(); | |
| auto sequenced_task_runner = thread_pool_->CreateSequencedTaskRunner({}); | |
| auto single_thread_task_runner = thread_pool_->CreateSingleThreadTaskRunner( | |
| {}, SingleThreadTaskRunnerThreadMode::SHARED); | |
| TestWaitableEvent task_ran; | |
| sequenced_task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce( | |
| [](scoped_refptr<SingleThreadTaskRunner> single_thread_task_runner, | |
| TestWaitableEvent* task_ran) { | |
| EXPECT_FALSE( | |
| single_thread_task_runner->RunsTasksInCurrentSequence()); | |
| task_ran->Signal(); | |
| }, | |
| single_thread_task_runner, Unretained(&task_ran))); | |
| task_ran.Wait(); | |
| } | |
| #if BUILDFLAG(IS_WIN) | |
| TEST_P(ThreadPoolImplTest, COMSTATaskRunnersRunWithCOMSTA) { | |
| StartThreadPool(); | |
| auto com_sta_task_runner = thread_pool_->CreateCOMSTATaskRunner( | |
| {}, SingleThreadTaskRunnerThreadMode::SHARED); | |
| TestWaitableEvent task_ran; | |
| com_sta_task_runner->PostTask( | |
| FROM_HERE, BindOnce( | |
| [](TestWaitableEvent* task_ran) { | |
| win::AssertComApartmentType(win::ComApartmentType::STA); | |
| task_ran->Signal(); | |
| }, | |
| Unretained(&task_ran))); | |
| task_ran.Wait(); | |
| } | |
| #endif // BUILDFLAG(IS_WIN) | |
| TEST_P(ThreadPoolImplTest, DelayedTasksNotRunAfterShutdown) { | |
| StartThreadPool(); | |
| // As with delayed tasks in general, this is racy. If the task does happen to | |
| // run after Shutdown within the timeout, it will fail this test. | |
| // | |
| // The timeout should be set sufficiently long enough to ensure that the | |
| // delayed task did not run. 2x is generally good enough. | |
| // | |
| // A non-racy way to do this would be to post two sequenced tasks: | |
| // 1) Regular Post Task: A WaitableEvent.Wait | |
| // 2) Delayed Task: ADD_FAILURE() | |
| // and signalling the WaitableEvent after Shutdown() on a different thread | |
| // since Shutdown() will block. However, the cost of managing this extra | |
| // thread was deemed to be too great for the unlikely race. | |
| thread_pool_->PostDelayedTask(FROM_HERE, {}, BindOnce([] { ADD_FAILURE(); }), | |
| TestTimeouts::tiny_timeout()); | |
| thread_pool_->Shutdown(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 2); | |
| } | |
| #if BUILDFLAG(IS_POSIX) | |
| TEST_P(ThreadPoolImplTest, FileDescriptorWatcherNoOpsAfterShutdown) { | |
| StartThreadPool(); | |
| int pipes[2]; | |
| ASSERT_EQ(0, pipe(pipes)); | |
| scoped_refptr<TaskRunner> blocking_task_runner = | |
| thread_pool_->CreateSequencedTaskRunner( | |
| {TaskShutdownBehavior::BLOCK_SHUTDOWN}); | |
| blocking_task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce( | |
| [](int read_fd) { | |
| std::unique_ptr<FileDescriptorWatcher::Controller> controller = | |
| FileDescriptorWatcher::WatchReadable( | |
| read_fd, BindRepeating([] { NOTREACHED(); })); | |
| // This test is for components that intentionally leak their | |
| // watchers at shutdown. We can't clean |controller| up because its | |
| // destructor will assert that it's being called from the correct | |
| // sequence. After the thread pool is shutdown, it is not | |
| // possible to run tasks on this sequence. | |
| // | |
| // Note: Do not inline the controller.release() call into the | |
| // ANNOTATE_LEAKING_OBJECT_PTR as the annotation is removed | |
| // by the preprocessor in non-LEAK_SANITIZER builds, | |
| // effectively breaking this test. | |
| ANNOTATE_LEAKING_OBJECT_PTR(controller.get()); | |
| controller.release(); | |
| }, | |
| pipes[0])); | |
| thread_pool_->Shutdown(); | |
| constexpr char kByte = '!'; | |
| ASSERT_TRUE(WriteFileDescriptor(pipes[1], byte_span_from_ref(kByte))); | |
| // Give a chance for the file watcher to fire before closing the handles. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_EQ(0, IGNORE_EINTR(close(pipes[0]))); | |
| EXPECT_EQ(0, IGNORE_EINTR(close(pipes[1]))); | |
| } | |
| #endif // BUILDFLAG(IS_POSIX) | |
| #if BUILDFLAG(IS_POSIX) | |
| // Verify that FileDescriptorWatcher::WatchReadable() can be called from task | |
| // running on a task_runner with GetExecutionMode() without a crash. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, FileDescriptorWatcher) { | |
| StartThreadPool(); | |
| int fds[2]; | |
| ASSERT_EQ(0, pipe(fds)); | |
| auto task_runner = CreateTaskRunnerAndExecutionMode( | |
| thread_pool_.get(), GetTraits(), GetExecutionMode()); | |
| EXPECT_TRUE(task_runner->PostTask( | |
| FROM_HERE, BindOnce(IgnoreResult(&FileDescriptorWatcher::WatchReadable), | |
| fds[0], DoNothing()))); | |
| thread_pool_->FlushForTesting(); | |
| EXPECT_EQ(0, IGNORE_EINTR(close(fds[0]))); | |
| EXPECT_EQ(0, IGNORE_EINTR(close(fds[1]))); | |
| } | |
| #endif | |
| // Verify that tasks posted on the same sequence access the same values on | |
| // SequenceLocalStorage, and tasks on different sequences see different values. | |
| TEST_P(ThreadPoolImplTest, SequenceLocalStorage) { | |
| StartThreadPool(); | |
| SequenceLocalStorageSlot<int> slot; | |
| auto sequenced_task_runner1 = thread_pool_->CreateSequencedTaskRunner({}); | |
| auto sequenced_task_runner2 = thread_pool_->CreateSequencedTaskRunner({}); | |
| sequenced_task_runner1->PostTask( | |
| FROM_HERE, | |
| BindOnce([](SequenceLocalStorageSlot<int>* slot) { slot->emplace(11); }, | |
| &slot)); | |
| sequenced_task_runner1->PostTask( | |
| FROM_HERE, BindOnce( | |
| [](SequenceLocalStorageSlot<int>* slot) { | |
| EXPECT_EQ(slot->GetOrCreateValue(), 11); | |
| }, | |
| &slot)); | |
| sequenced_task_runner2->PostTask( | |
| FROM_HERE, BindOnce( | |
| [](SequenceLocalStorageSlot<int>* slot) { | |
| EXPECT_NE(slot->GetOrCreateValue(), 11); | |
| }, | |
| &slot)); | |
| thread_pool_->FlushForTesting(); | |
| } | |
| TEST_P(ThreadPoolImplTest, FlushAsyncNoTasks) { | |
| StartThreadPool(); | |
| bool called_back = false; | |
| thread_pool_->FlushAsyncForTesting( | |
| BindOnce([](bool* called_back) { *called_back = true; }, | |
| Unretained(&called_back))); | |
| EXPECT_TRUE(called_back); | |
| } | |
| TEST_P(ThreadPoolImplTest, CreateSequencedTaskRunnerForResource) { | |
| StartThreadPool(); | |
| scoped_refptr<SequencedTaskRunner> task_runner1 = | |
| thread_pool_->CreateSequencedTaskRunnerForResource( | |
| {}, base::FilePath(FILE_PATH_LITERAL("Resource1"))); | |
| scoped_refptr<SequencedTaskRunner> task_runner2 = | |
| thread_pool_->CreateSequencedTaskRunnerForResource( | |
| {}, base::FilePath(FILE_PATH_LITERAL("Resource2"))); | |
| scoped_refptr<SequencedTaskRunner> task_runner3 = | |
| thread_pool_->CreateSequencedTaskRunnerForResource( | |
| {}, base::FilePath(FILE_PATH_LITERAL("Resource1"))); | |
| EXPECT_NE(task_runner1, task_runner2); | |
| EXPECT_EQ(task_runner1, task_runner3); | |
| } | |
| namespace { | |
| // Verifies that all strings passed as argument are found on the current stack. | |
| // Ignores failures if this configuration doesn't have symbols. | |
| void VerifyHasStringsOnStack(const std::string& pool_str, | |
| const std::string& shutdown_behavior_str) { | |
| const std::string stack = debug::StackTrace().ToString(); | |
| SCOPED_TRACE(stack); | |
| const bool stack_has_symbols = | |
| stack.find("WorkerThread") != std::string::npos; | |
| if (!stack_has_symbols) { | |
| return; | |
| } | |
| EXPECT_THAT(stack, ::testing::HasSubstr(pool_str)); | |
| EXPECT_THAT(stack, ::testing::HasSubstr(shutdown_behavior_str)); | |
| } | |
| } // namespace | |
| #if BUILDFLAG(IS_POSIX) | |
| // Many POSIX bots flakily crash on |debug::StackTrace().ToString()|, | |
| // https://crbug.com/840429. | |
| #define MAYBE_IdentifiableStacks DISABLED_IdentifiableStacks | |
| #elif BUILDFLAG(IS_WIN) && \ | |
| (defined(ADDRESS_SANITIZER) || BUILDFLAG(CFI_CAST_CHECK)) | |
| // Hangs on WinASan and WinCFI (grabbing StackTrace() too slow?), | |
| // https://crbug.com/845010#c7. | |
| #define MAYBE_IdentifiableStacks DISABLED_IdentifiableStacks | |
| #else | |
| #define MAYBE_IdentifiableStacks IdentifiableStacks | |
| #endif | |
| // Integration test that verifies that workers have a frame on their stacks | |
| // which easily identifies the type of worker and shutdown behavior (useful to | |
| // diagnose issues from logs without memory dumps). | |
| TEST_P(ThreadPoolImplTest, MAYBE_IdentifiableStacks) { | |
| StartThreadPool(); | |
| // Shutdown behaviors and expected stack frames. | |
| constexpr std::pair<TaskShutdownBehavior, const char*> shutdown_behaviors[] = | |
| {{TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, "RunContinueOnShutdown"}, | |
| {TaskShutdownBehavior::SKIP_ON_SHUTDOWN, "RunSkipOnShutdown"}, | |
| {TaskShutdownBehavior::BLOCK_SHUTDOWN, "RunBlockShutdown"}}; | |
| for (const auto& shutdown_behavior : shutdown_behaviors) { | |
| const TaskTraits traits = {shutdown_behavior.first}; | |
| const TaskTraits best_effort_traits = {shutdown_behavior.first, | |
| TaskPriority::BEST_EFFORT}; | |
| thread_pool_->CreateSequencedTaskRunner(traits)->PostTask( | |
| FROM_HERE, BindOnce(&VerifyHasStringsOnStack, "RunPooledWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_->CreateSequencedTaskRunner(best_effort_traits) | |
| ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringsOnStack, | |
| "RunBackgroundPooledWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateSingleThreadTaskRunner(traits, | |
| SingleThreadTaskRunnerThreadMode::SHARED) | |
| ->PostTask(FROM_HERE, | |
| BindOnce(&VerifyHasStringsOnStack, "RunSharedWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateSingleThreadTaskRunner(best_effort_traits, | |
| SingleThreadTaskRunnerThreadMode::SHARED) | |
| ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringsOnStack, | |
| "RunBackgroundSharedWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateSingleThreadTaskRunner( | |
| traits, SingleThreadTaskRunnerThreadMode::DEDICATED) | |
| ->PostTask(FROM_HERE, | |
| BindOnce(&VerifyHasStringsOnStack, "RunDedicatedWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateSingleThreadTaskRunner( | |
| best_effort_traits, SingleThreadTaskRunnerThreadMode::DEDICATED) | |
| ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringsOnStack, | |
| "RunBackgroundDedicatedWorker", | |
| shutdown_behavior.second)); | |
| #if BUILDFLAG(IS_WIN) | |
| thread_pool_ | |
| ->CreateCOMSTATaskRunner(traits, | |
| SingleThreadTaskRunnerThreadMode::SHARED) | |
| ->PostTask(FROM_HERE, | |
| BindOnce(&VerifyHasStringsOnStack, "RunSharedCOMWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateCOMSTATaskRunner(best_effort_traits, | |
| SingleThreadTaskRunnerThreadMode::SHARED) | |
| ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringsOnStack, | |
| "RunBackgroundSharedCOMWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateCOMSTATaskRunner(traits, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED) | |
| ->PostTask(FROM_HERE, | |
| BindOnce(&VerifyHasStringsOnStack, "RunDedicatedCOMWorker", | |
| shutdown_behavior.second)); | |
| thread_pool_ | |
| ->CreateCOMSTATaskRunner(best_effort_traits, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED) | |
| ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringsOnStack, | |
| "RunBackgroundDedicatedCOMWorker", | |
| shutdown_behavior.second)); | |
| #endif // BUILDFLAG(IS_WIN) | |
| } | |
| thread_pool_->FlushForTesting(); | |
| } | |
| TEST_P(ThreadPoolImplTest, WorkerThreadObserver) { | |
| auto owned_observer = | |
| std::make_unique<testing::StrictMock<test::MockWorkerThreadObserver>>(); | |
| auto* observer = owned_observer.get(); | |
| set_worker_thread_observer(std::move(owned_observer)); | |
| // A worker should be created for each thread group. After that, 4 threads | |
| // should be created for each SingleThreadTaskRunnerThreadMode (8 on Windows). | |
| const int kExpectedNumForegroundPoolWorkers = 1; | |
| const int kExpectedNumUtilityPoolWorkers = | |
| GetUseResourceEfficientThreadGroup() && | |
| CanUseUtilityThreadTypeForWorkerThread() | |
| ? 1 | |
| : 0; | |
| const int kExpectedNumHighPriorityPoolWorkers = | |
| GetUseHighPriorityThreadGroup() ? 2 : 0; | |
| const int kExpectedNumBackgroundPoolWorkers = | |
| CanUseBackgroundThreadTypeForWorkerThread() ? 1 : 0; | |
| const int kExpectedNumPoolWorkers = | |
| kExpectedNumHighPriorityPoolWorkers + kExpectedNumForegroundPoolWorkers + | |
| kExpectedNumUtilityPoolWorkers + kExpectedNumBackgroundPoolWorkers; | |
| const int kExpectedNumSharedSingleThreadedForegroundWorkers = 2; | |
| const int kExpectedNumSharedSingleThreadedUtilityWorkers = | |
| GetUseResourceEfficientThreadGroup() && | |
| CanUseUtilityThreadTypeForWorkerThread() | |
| ? 2 | |
| : 0; | |
| const int kExpectedNumSharedSingleThreadedBackgroundWorkers = | |
| CanUseBackgroundThreadTypeForWorkerThread() ? 2 : 0; | |
| const int kExpectedNumSharedSingleThreadedWorkers = | |
| kExpectedNumSharedSingleThreadedForegroundWorkers + | |
| kExpectedNumSharedSingleThreadedUtilityWorkers + | |
| kExpectedNumSharedSingleThreadedBackgroundWorkers; | |
| const int kExpectedNumDedicatedSingleThreadedWorkers = 6; | |
| const int kExpectedNumCOMSharedSingleThreadedWorkers = | |
| #if BUILDFLAG(IS_WIN) | |
| kExpectedNumSharedSingleThreadedWorkers; | |
| #else | |
| 0; | |
| #endif | |
| const int kExpectedNumCOMDedicatedSingleThreadedWorkers = | |
| #if BUILDFLAG(IS_WIN) | |
| kExpectedNumDedicatedSingleThreadedWorkers; | |
| #else | |
| 0; | |
| #endif | |
| EXPECT_CALL(*observer, OnWorkerThreadMainEntry()) | |
| .Times(kExpectedNumPoolWorkers + kExpectedNumSharedSingleThreadedWorkers + | |
| kExpectedNumDedicatedSingleThreadedWorkers + | |
| kExpectedNumCOMSharedSingleThreadedWorkers + | |
| kExpectedNumCOMDedicatedSingleThreadedWorkers); | |
| // Infinite detach time to prevent workers from invoking | |
| // OnWorkerThreadMainExit() earlier than expected. | |
| StartThreadPool(kMaxNumForegroundThreads, kMaxNumUtilityThreads, | |
| kMaxNumAudioThreads, TimeDelta::Max()); | |
| std::vector<scoped_refptr<SingleThreadTaskRunner>> task_runners; | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::BEST_EFFORT}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::BEST_EFFORT, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_VISIBLE}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_VISIBLE, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_BLOCKING, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::BEST_EFFORT}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::BEST_EFFORT, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_VISIBLE}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_VISIBLE, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_BLOCKING}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateSingleThreadTaskRunner( | |
| {TaskPriority::USER_BLOCKING, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| #if BUILDFLAG(IS_WIN) | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::BEST_EFFORT}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::BEST_EFFORT, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_VISIBLE}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_VISIBLE, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_BLOCKING, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::SHARED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::BEST_EFFORT}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::BEST_EFFORT, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_VISIBLE}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_VISIBLE, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_BLOCKING}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| task_runners.push_back(thread_pool_->CreateCOMSTATaskRunner( | |
| {TaskPriority::USER_BLOCKING, MayBlock()}, | |
| SingleThreadTaskRunnerThreadMode::DEDICATED)); | |
| #endif | |
| for (auto& task_runner : task_runners) { | |
| task_runner->PostTask(FROM_HERE, DoNothing()); | |
| } | |
| // Release single-threaded workers. This should cause dedicated workers to | |
| // invoke OnWorkerThreadMainExit(). | |
| observer->AllowCallsOnMainExit(kExpectedNumDedicatedSingleThreadedWorkers + | |
| kExpectedNumCOMDedicatedSingleThreadedWorkers); | |
| task_runners.clear(); | |
| observer->WaitCallsOnMainExit(); | |
| // Join all remaining workers. This should cause shared single-threaded | |
| // workers and thread pool workers to invoke OnWorkerThreadMainExit(). | |
| observer->AllowCallsOnMainExit(kExpectedNumPoolWorkers + | |
| kExpectedNumSharedSingleThreadedWorkers + | |
| kExpectedNumCOMSharedSingleThreadedWorkers); | |
| TearDown(); | |
| observer->WaitCallsOnMainExit(); | |
| } | |
| // Verify a basic EnqueueJobTaskSource() runs the worker task. | |
| TEST_P(ThreadPoolImplTest, ScheduleJobTaskSource) { | |
| StartThreadPool(); | |
| TestWaitableEvent threads_running; | |
| auto job_task = base::MakeRefCounted<test::MockJobTask>( | |
| BindLambdaForTesting( | |
| [&threads_running](JobDelegate*) { threads_running.Signal(); }), | |
| /* num_tasks_to_run */ 1); | |
| scoped_refptr<JobTaskSource> task_source = | |
| job_task->GetJobTaskSource(FROM_HERE, {}, thread_pool_.get()); | |
| thread_pool_->EnqueueJobTaskSource(task_source); | |
| threads_running.Wait(); | |
| } | |
| // Verify that calling ShouldYield() returns true for a job task source that | |
| // needs to change thread group because of a priority update. | |
| TEST_P(ThreadPoolImplTest, ThreadGroupChangeShouldYield) { | |
| StartThreadPool(); | |
| TestWaitableEvent threads_running; | |
| TestWaitableEvent threads_continue; | |
| auto job_task = base::MakeRefCounted<test::MockJobTask>( | |
| BindLambdaForTesting( | |
| [&threads_running, &threads_continue](JobDelegate* delegate) { | |
| EXPECT_FALSE(delegate->ShouldYield()); | |
| threads_running.Signal(); | |
| threads_continue.Wait(); | |
| // The task source needs to yield if background thread groups exist. | |
| EXPECT_EQ(delegate->ShouldYield(), | |
| CanUseBackgroundThreadTypeForWorkerThread()); | |
| }), | |
| /* num_tasks_to_run */ 1); | |
| scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource( | |
| FROM_HERE, {TaskPriority::USER_VISIBLE}, thread_pool_.get()); | |
| thread_pool_->EnqueueJobTaskSource(task_source); | |
| threads_running.Wait(); | |
| thread_pool_->UpdatePriority(task_source, TaskPriority::BEST_EFFORT); | |
| threads_continue.Signal(); | |
| // Flush the task tracker to be sure that no local variables are accessed by | |
| // tasks after the end of the scope. | |
| thread_pool_->FlushForTesting(); | |
| } | |
| namespace { | |
| class MustBeDestroyed { | |
| public: | |
| explicit MustBeDestroyed(bool* was_destroyed) | |
| : was_destroyed_(was_destroyed) {} | |
| MustBeDestroyed(const MustBeDestroyed&) = delete; | |
| MustBeDestroyed& operator=(const MustBeDestroyed&) = delete; | |
| ~MustBeDestroyed() { *was_destroyed_ = true; } | |
| private: | |
| const raw_ptr<bool> was_destroyed_; | |
| }; | |
| } // namespace | |
| // Regression test for https://crbug.com/945087. | |
| TEST_P(ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| NoLeakWhenPostingNestedTask) { | |
| StartThreadPool(); | |
| SequenceLocalStorageSlot<std::unique_ptr<MustBeDestroyed>> sls; | |
| bool was_destroyed = false; | |
| auto must_be_destroyed = std::make_unique<MustBeDestroyed>(&was_destroyed); | |
| auto task_runner = CreateTaskRunnerAndExecutionMode( | |
| thread_pool_.get(), GetTraits(), GetExecutionMode()); | |
| task_runner->PostTask(FROM_HERE, BindLambdaForTesting([&] { | |
| sls.emplace(std::move(must_be_destroyed)); | |
| task_runner->PostTask(FROM_HERE, DoNothing()); | |
| })); | |
| TearDown(); | |
| // The TaskRunner should be deleted along with the Sequence and its | |
| // SequenceLocalStorage when dropping this reference. | |
| task_runner = nullptr; | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| namespace { | |
| struct TaskRunnerAndEvents { | |
| TaskRunnerAndEvents(scoped_refptr<UpdateableSequencedTaskRunner> task_runner, | |
| const TaskPriority updated_priority, | |
| TestWaitableEvent* expected_previous_event) | |
| : task_runner(std::move(task_runner)), | |
| updated_priority(updated_priority), | |
| expected_previous_event(expected_previous_event) {} | |
| // The UpdateableSequencedTaskRunner. | |
| scoped_refptr<UpdateableSequencedTaskRunner> task_runner; | |
| // The priority to use in UpdatePriority(). | |
| const TaskPriority updated_priority; | |
| // Signaled when a task blocking |task_runner| is scheduled. | |
| TestWaitableEvent scheduled; | |
| // Signaled to release the task blocking |task_runner|. | |
| TestWaitableEvent blocked; | |
| // Signaled in the task that runs following the priority update. | |
| TestWaitableEvent task_ran; | |
| // An event that should be signaled before the task following the priority | |
| // update runs. | |
| raw_ptr<TestWaitableEvent> expected_previous_event; | |
| }; | |
| // Create a series of sample task runners that will post tasks at various | |
| // initial priorities, then update priority. | |
| std::vector<std::unique_ptr<TaskRunnerAndEvents>> CreateTaskRunnersAndEvents( | |
| ThreadPoolImplTest* test, | |
| ThreadPolicy thread_policy) { | |
| ThreadPoolImpl* thread_pool = test->thread_pool_.get(); | |
| std::vector<std::unique_ptr<TaskRunnerAndEvents>> task_runners_and_events; | |
| // ----- | |
| // Task runner that will start as USER_VISIBLE and update to USER_BLOCKING. | |
| // Its task is expected to run first. | |
| task_runners_and_events.push_back(std::make_unique<TaskRunnerAndEvents>( | |
| thread_pool->CreateUpdateableSequencedTaskRunner( | |
| TaskTraits({TaskPriority::USER_VISIBLE, thread_policy})), | |
| TaskPriority::USER_BLOCKING, nullptr)); | |
| // ----- | |
| // Task runner that will start as BEST_EFFORT and update to USER_VISIBLE. | |
| // Its task is expected to run after the USER_BLOCKING task runner's task, | |
| // unless resource-efficient thread group exists, in which case they will run | |
| // asynchronously. | |
| TestWaitableEvent* expected_previous_event = | |
| test->GetUseResourceEfficientThreadGroup() | |
| ? nullptr | |
| : &task_runners_and_events.back()->task_ran; | |
| task_runners_and_events.push_back(std::make_unique<TaskRunnerAndEvents>( | |
| thread_pool->CreateUpdateableSequencedTaskRunner( | |
| {TaskPriority::BEST_EFFORT, thread_policy}), | |
| TaskPriority::USER_VISIBLE, expected_previous_event)); | |
| // ----- | |
| // Task runner that will start as USER_BLOCKING and update to BEST_EFFORT. Its | |
| // task is expected to run asynchronously with the other two task runners' | |
| // tasks if background thread groups exist, or after the USER_VISIBLE task | |
| // runner's task if not. | |
| // | |
| // If the task following the priority update is expected to run in the | |
| // foreground group, it should be after the task posted to the TaskRunner | |
| // whose priority is updated to USER_VISIBLE. | |
| expected_previous_event = | |
| CanUseBackgroundThreadTypeForWorkerThread() || | |
| (test->GetUseResourceEfficientThreadGroup() && | |
| CanUseUtilityThreadTypeForWorkerThread()) | |
| ? nullptr | |
| : &task_runners_and_events.back()->task_ran; | |
| task_runners_and_events.push_back(std::make_unique<TaskRunnerAndEvents>( | |
| thread_pool->CreateUpdateableSequencedTaskRunner( | |
| TaskTraits({TaskPriority::USER_BLOCKING, thread_policy})), | |
| TaskPriority::BEST_EFFORT, expected_previous_event)); | |
| return task_runners_and_events; | |
| } | |
| // Update the priority of a sequence when it is not scheduled. | |
| void TestUpdatePrioritySequenceNotScheduled(ThreadPoolImplTest* test, | |
| ThreadPolicy thread_policy) { | |
| // This test verifies that tasks run in priority order. With more than 1 | |
| // thread per pool, it is possible that tasks don't run in order even if | |
| // threads got tasks from the PriorityQueue in order. Therefore, enforce a | |
| // maximum of 1 thread per pool. | |
| constexpr size_t kLocalMaxNumForegroundThreads = 1; | |
| test->StartThreadPool(kLocalMaxNumForegroundThreads); | |
| auto task_runners_and_events = | |
| CreateTaskRunnersAndEvents(test, thread_policy); | |
| // Prevent tasks from running. | |
| test->thread_pool_->BeginFence(); | |
| // Post tasks to multiple task runners while they are at initial priority. | |
| // They won't run immediately because of the call to BeginFence() above. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce( | |
| &VerifyOrderAndTaskEnvironmentAndSignalEvent, | |
| TaskTraits{task_runner_and_events->updated_priority, thread_policy}, | |
| test->GetUseResourceEfficientThreadGroup(), ThreadType::kDefault, | |
| Unretained(task_runner_and_events->expected_previous_event.get()), | |
| Unretained(&task_runner_and_events->task_ran))); | |
| } | |
| // Update the priorities of the task runners that posted the tasks. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_runner->UpdatePriority( | |
| task_runner_and_events->updated_priority); | |
| } | |
| // Allow tasks to run. | |
| test->thread_pool_->EndFence(); | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_ran.Wait(); | |
| } | |
| } | |
| // Update the priority of a sequence when it is scheduled, i.e. not currently | |
| // in a priority queue. | |
| void TestUpdatePrioritySequenceScheduled(ThreadPoolImplTest* test, | |
| ThreadPolicy thread_policy) { | |
| test->StartThreadPool(); | |
| auto task_runners_and_events = | |
| CreateTaskRunnersAndEvents(test, thread_policy); | |
| // Post blocking tasks to all task runners to prevent tasks from being | |
| // scheduled later in the test. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_runner->PostTask( | |
| FROM_HERE, BindLambdaForTesting([&] { | |
| task_runner_and_events->scheduled.Signal(); | |
| task_runner_and_events->blocked.Wait(); | |
| })); | |
| task_runner_and_events->scheduled.Wait(); | |
| } | |
| // Update the priorities of the task runners while they are scheduled and | |
| // blocked. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_runner->UpdatePriority( | |
| task_runner_and_events->updated_priority); | |
| } | |
| // Post an additional task to each task runner. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->task_runner->PostTask( | |
| FROM_HERE, | |
| BindOnce( | |
| &VerifyOrderAndTaskEnvironmentAndSignalEvent, | |
| TaskTraits{task_runner_and_events->updated_priority, thread_policy}, | |
| test->GetUseResourceEfficientThreadGroup(), ThreadType::kDefault, | |
| Unretained(task_runner_and_events->expected_previous_event), | |
| Unretained(&task_runner_and_events->task_ran))); | |
| } | |
| // Unblock the task blocking each task runner, allowing the additional posted | |
| // tasks to run. Each posted task will verify that it has been posted with | |
| // updated priority when it runs. | |
| for (auto& task_runner_and_events : task_runners_and_events) { | |
| task_runner_and_events->blocked.Signal(); | |
| task_runner_and_events->task_ran.Wait(); | |
| } | |
| } | |
| } // namespace | |
| TEST_P(ThreadPoolImplTest, | |
| UpdatePrioritySequenceNotScheduled_PreferBackground) { | |
| TestUpdatePrioritySequenceNotScheduled(this, ThreadPolicy::PREFER_BACKGROUND); | |
| } | |
| TEST_P(ThreadPoolImplTest, | |
| UpdatePrioritySequenceNotScheduled_MustUseForeground) { | |
| TestUpdatePrioritySequenceNotScheduled(this, | |
| ThreadPolicy::MUST_USE_FOREGROUND); | |
| } | |
| TEST_P(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_PreferBackground) { | |
| TestUpdatePrioritySequenceScheduled(this, ThreadPolicy::PREFER_BACKGROUND); | |
| } | |
| TEST_P(ThreadPoolImplTest, UpdatePrioritySequenceScheduled_MustUseForeground) { | |
| TestUpdatePrioritySequenceScheduled(this, ThreadPolicy::MUST_USE_FOREGROUND); | |
| } | |
| // Verify that a ThreadPolicy has to be specified in TaskTraits to increase | |
| // TaskPriority from BEST_EFFORT. | |
| TEST_P(ThreadPoolImplTest, UpdatePriorityFromBestEffortNoThreadPolicy) { | |
| GTEST_FLAG_SET(death_test_style, "threadsafe"); | |
| StartThreadPool(); | |
| { | |
| auto task_runner = thread_pool_->CreateUpdateableSequencedTaskRunner( | |
| {TaskPriority::BEST_EFFORT}); | |
| EXPECT_DCHECK_DEATH( | |
| { task_runner->UpdatePriority(TaskPriority::USER_VISIBLE); }); | |
| } | |
| { | |
| auto task_runner = thread_pool_->CreateUpdateableSequencedTaskRunner( | |
| {TaskPriority::BEST_EFFORT}); | |
| EXPECT_DCHECK_DEATH( | |
| { task_runner->UpdatePriority(TaskPriority::USER_BLOCKING); }); | |
| } | |
| } | |
| // Verifies that a BEST_EFFORT task runs during FlushForTesting() even if a | |
| // BestEffortFence is active. | |
| TEST_P(ThreadPoolImplTest, BestEffortFenceLiftedForFlush) { | |
| StartThreadPool(); | |
| TestWaitableEvent best_effort_task_ran; | |
| thread_pool_->BeginBestEffortFence(); | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, {TaskPriority::BEST_EFFORT}, | |
| BindOnce(&TestWaitableEvent::Signal, Unretained(&best_effort_task_ran)), | |
| base::TimeDelta()); | |
| // The task shouldn't run while the fence is up. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(best_effort_task_ran.IsSignaled()); | |
| // FlushForTesting() should run the task even with the fence. | |
| thread_pool_->FlushForTesting(); | |
| EXPECT_TRUE(best_effort_task_ran.IsSignaled()); | |
| } | |
| // Verifies that user visible tasks run during FlushForTesting() even if a fence | |
| // is active. | |
| TEST_P(ThreadPoolImplTest, FenceLiftedForFlush) { | |
| StartThreadPool(); | |
| TestWaitableEvent task_ran; | |
| thread_pool_->BeginFence(); | |
| thread_pool_->PostDelayedTask( | |
| FROM_HERE, {TaskPriority::USER_VISIBLE}, | |
| BindOnce(&TestWaitableEvent::Signal, Unretained(&task_ran)), | |
| base::TimeDelta()); | |
| // The task shouldn't run while the fence is up. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(task_ran.IsSignaled()); | |
| // FlushForTesting() should run the task even with the fence. | |
| thread_pool_->FlushForTesting(); | |
| EXPECT_TRUE(task_ran.IsSignaled()); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(All, | |
| ThreadPoolImplTest, | |
| ::testing::Combine(::testing::Bool(), | |
| ::testing::Bool(), | |
| ::testing::Bool())); | |
| INSTANTIATE_TEST_SUITE_P( | |
| All, | |
| ThreadPoolImplTest_CoverAllSchedulingOptions, | |
| ::testing::Combine( | |
| ::testing::Bool(), | |
| ::testing::ValuesIn( | |
| GetTraitsExecutionModePairsToCoverAllSchedulingOptions()))); | |
| } // namespace base::internal |