// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/task_scheduler/task_scheduler_impl.h" #include #include #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/cfi_buildflags.h" #include "base/debug/stack_trace.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/metrics/field_trial.h" #include "base/metrics/field_trial_params.h" #include "base/synchronization/waitable_event.h" #include "base/task_scheduler/environment_config.h" #include "base/task_scheduler/scheduler_worker_observer.h" #include "base/task_scheduler/scheduler_worker_pool_params.h" #include "base/task_scheduler/task_traits.h" #include "base/task_scheduler/test_task_factory.h" #include "base/task_scheduler/test_utils.h" #include "base/test/gtest_util.h" #include "base/test/test_timeouts.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 defined(OS_POSIX) #include #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 // defined(OS_POSIX) #if defined(OS_WIN) #include "base/win/com_init_util.h" #endif // defined(OS_WIN) namespace base { namespace internal { namespace { struct TraitsExecutionModePair { TraitsExecutionModePair(const TaskTraits& traits, test::ExecutionMode execution_mode) : traits(traits), execution_mode(execution_mode) {} TaskTraits traits; test::ExecutionMode execution_mode; }; #if DCHECK_IS_ON() // Returns whether I/O calls are allowed on the current thread. bool GetIOAllowed() { const bool previous_value = ThreadRestrictions::SetIOAllowed(true); ThreadRestrictions::SetIOAllowed(previous_value); return previous_value; } #endif // Verify that the current thread priority and I/O restrictions are appropriate // to run a Task with |traits|. // Note: ExecutionMode is verified inside TestTaskFactory. void VerifyTaskEnvironment(const TaskTraits& traits) { EXPECT_EQ(CanUseBackgroundPriorityForSchedulerWorker() && traits.priority() == TaskPriority::BACKGROUND ? ThreadPriority::BACKGROUND : ThreadPriority::NORMAL, PlatformThread::GetCurrentThreadPriority()); #if DCHECK_IS_ON() // The #if above is required because GetIOAllowed() always returns true when // !DCHECK_IS_ON(), even when |traits| don't allow file I/O. EXPECT_EQ(traits.may_block(), GetIOAllowed()); #endif // Verify that the thread the task is running on is named as expected. const std::string current_thread_name(PlatformThread::GetName()); EXPECT_NE(std::string::npos, current_thread_name.find("TaskScheduler")); if (current_thread_name.find("SingleThread") != std::string::npos) { // For now, single-threaded background tasks run on their own threads. // TODO(fdoray): Run single-threaded background tasks on foreground workers // on platforms that don't support background thread priority. EXPECT_NE( std::string::npos, current_thread_name.find(traits.priority() == TaskPriority::BACKGROUND ? "Background" : "Foreground")); } else { EXPECT_NE(std::string::npos, current_thread_name.find( CanUseBackgroundPriorityForSchedulerWorker() && traits.priority() == TaskPriority::BACKGROUND ? "Background" : "Foreground")); } EXPECT_EQ(traits.may_block(), current_thread_name.find("Blocking") != std::string::npos); } void VerifyTaskEnvironmentAndSignalEvent(const TaskTraits& traits, WaitableEvent* event) { DCHECK(event); VerifyTaskEnvironment(traits); event->Signal(); } void VerifyTimeAndTaskEnvironmentAndSignalEvent(const TaskTraits& traits, TimeTicks expected_time, WaitableEvent* event) { DCHECK(event); EXPECT_LE(expected_time, TimeTicks::Now()); VerifyTaskEnvironment(traits); event->Signal(); } scoped_refptr CreateTaskRunnerWithTraitsAndExecutionMode( TaskScheduler* scheduler, const TaskTraits& traits, test::ExecutionMode execution_mode, SingleThreadTaskRunnerThreadMode default_single_thread_task_runner_mode = SingleThreadTaskRunnerThreadMode::SHARED) { switch (execution_mode) { case test::ExecutionMode::PARALLEL: return scheduler->CreateTaskRunnerWithTraits(traits); case test::ExecutionMode::SEQUENCED: return scheduler->CreateSequencedTaskRunnerWithTraits(traits); case test::ExecutionMode::SINGLE_THREADED: { return scheduler->CreateSingleThreadTaskRunnerWithTraits( traits, default_single_thread_task_runner_mode); } } ADD_FAILURE() << "Unknown ExecutionMode"; return nullptr; } class ThreadPostingTasks : public SimpleThread { public: // Creates a thread that posts Tasks to |scheduler| with |traits| and // |execution_mode|. ThreadPostingTasks(TaskSchedulerImpl* scheduler, const TaskTraits& traits, test::ExecutionMode execution_mode) : SimpleThread("ThreadPostingTasks"), traits_(traits), factory_(CreateTaskRunnerWithTraitsAndExecutionMode(scheduler, traits, execution_mode), execution_mode) {} void WaitForAllTasksToRun() { factory_.WaitForAllTasksToRun(); } private: void Run() override { EXPECT_FALSE(factory_.task_runner()->RunsTasksInCurrentSequence()); const size_t kNumTasksPerThread = 150; for (size_t i = 0; i < kNumTasksPerThread; ++i) { factory_.PostTask(test::TestTaskFactory::PostNestedTask::NO, Bind(&VerifyTaskEnvironment, traits_)); } } const TaskTraits traits_; test::TestTaskFactory factory_; DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasks); }; // Returns a vector with a TraitsExecutionModePair for each valid // combination of {ExecutionMode, TaskPriority, MayBlock()}. std::vector GetTraitsExecutionModePairs() { std::vector params; const test::ExecutionMode execution_modes[] = { test::ExecutionMode::PARALLEL, test::ExecutionMode::SEQUENCED, test::ExecutionMode::SINGLE_THREADED}; for (test::ExecutionMode execution_mode : execution_modes) { for (size_t priority_index = static_cast(TaskPriority::LOWEST); priority_index <= static_cast(TaskPriority::HIGHEST); ++priority_index) { const TaskPriority priority = static_cast(priority_index); params.push_back(TraitsExecutionModePair({priority}, execution_mode)); params.push_back(TraitsExecutionModePair({MayBlock()}, execution_mode)); } } return params; } class TaskSchedulerImplTest : public testing::TestWithParam { protected: TaskSchedulerImplTest() : scheduler_("Test"), field_trial_list_(nullptr) {} void EnableAllTasksUserBlocking() { constexpr char kFieldTrialName[] = "BrowserScheduler"; constexpr char kFieldTrialTestGroup[] = "DummyGroup"; std::map variation_params; variation_params["AllTasksUserBlocking"] = "true"; base::AssociateFieldTrialParams(kFieldTrialName, kFieldTrialTestGroup, variation_params); base::FieldTrialList::CreateFieldTrial(kFieldTrialName, kFieldTrialTestGroup); } void set_scheduler_worker_observer( SchedulerWorkerObserver* scheduler_worker_observer) { scheduler_worker_observer_ = scheduler_worker_observer; } void StartTaskScheduler() { constexpr TimeDelta kSuggestedReclaimTime = TimeDelta::FromSeconds(30); constexpr int kMaxNumBackgroundThreads = 1; constexpr int kMaxNumBackgroundBlockingThreads = 3; constexpr int kMaxNumForegroundThreads = 4; constexpr int kMaxNumForegroundBlockingThreads = 12; scheduler_.Start( {{kMaxNumBackgroundThreads, kSuggestedReclaimTime}, {kMaxNumBackgroundBlockingThreads, kSuggestedReclaimTime}, {kMaxNumForegroundThreads, kSuggestedReclaimTime}, {kMaxNumForegroundBlockingThreads, kSuggestedReclaimTime}}, scheduler_worker_observer_); } void TearDown() override { if (did_tear_down_) return; scheduler_.FlushForTesting(); scheduler_.JoinForTesting(); did_tear_down_ = true; } TaskSchedulerImpl scheduler_; private: base::FieldTrialList field_trial_list_; SchedulerWorkerObserver* scheduler_worker_observer_ = nullptr; bool did_tear_down_ = false; DISALLOW_COPY_AND_ASSIGN(TaskSchedulerImplTest); }; } // namespace // Verifies that a Task posted via PostDelayedTaskWithTraits 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(TaskSchedulerImplTest, PostDelayedTaskWithTraitsNoDelay) { StartTaskScheduler(); WaitableEvent task_ran; scheduler_.PostDelayedTaskWithTraits( FROM_HERE, GetParam().traits, BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits, Unretained(&task_ran)), TimeDelta()); task_ran.Wait(); } // Verifies that a Task posted via PostDelayedTaskWithTraits 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(TaskSchedulerImplTest, PostDelayedTaskWithTraitsWithDelay) { StartTaskScheduler(); WaitableEvent task_ran; scheduler_.PostDelayedTaskWithTraits( FROM_HERE, GetParam().traits, BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits, TimeTicks::Now() + TestTimeouts::tiny_timeout(), Unretained(&task_ran)), TestTimeouts::tiny_timeout()); 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(TaskSchedulerImplTest, PostTasksViaTaskRunner) { StartTaskScheduler(); test::TestTaskFactory factory( CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits, GetParam().execution_mode), GetParam().execution_mode); EXPECT_FALSE(factory.task_runner()->RunsTasksInCurrentSequence()); const size_t kNumTasksPerTest = 150; for (size_t i = 0; i < kNumTasksPerTest; ++i) { factory.PostTask(test::TestTaskFactory::PostNestedTask::NO, Bind(&VerifyTaskEnvironment, GetParam().traits)); } factory.WaitForAllTasksToRun(); } // Verifies that a task posted via PostDelayedTaskWithTraits without a delay // doesn't run before Start() is called. TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsNoDelayBeforeStart) { WaitableEvent task_running; scheduler_.PostDelayedTaskWithTraits( FROM_HERE, GetParam().traits, BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits, Unretained(&task_running)), 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()); StartTaskScheduler(); task_running.Wait(); } // Verifies that a task posted via PostDelayedTaskWithTraits with a delay // doesn't run before Start() is called. TEST_P(TaskSchedulerImplTest, PostDelayedTaskWithTraitsWithDelayBeforeStart) { WaitableEvent task_running; scheduler_.PostDelayedTaskWithTraits( FROM_HERE, GetParam().traits, BindOnce(&VerifyTimeAndTaskEnvironmentAndSignalEvent, GetParam().traits, 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()); StartTaskScheduler(); task_running.Wait(); } // Verifies that a task posted via a TaskRunner doesn't run before Start() is // called. TEST_P(TaskSchedulerImplTest, PostTaskViaTaskRunnerBeforeStart) { WaitableEvent task_running; CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits, GetParam().execution_mode) ->PostTask(FROM_HERE, BindOnce(&VerifyTaskEnvironmentAndSignalEvent, GetParam().traits, Unretained(&task_running))); // 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()); StartTaskScheduler(); // This should not hang if the task runs after Start(). task_running.Wait(); } // Verify that all tasks posted to a TaskRunner after Start() run in a // USER_BLOCKING environment when the AllTasksUserBlocking variation param of // the BrowserScheduler experiment is true. TEST_P(TaskSchedulerImplTest, AllTasksAreUserBlockingTaskRunner) { EnableAllTasksUserBlocking(); StartTaskScheduler(); WaitableEvent task_running; CreateTaskRunnerWithTraitsAndExecutionMode(&scheduler_, GetParam().traits, GetParam().execution_mode) ->PostTask(FROM_HERE, BindOnce(&VerifyTaskEnvironmentAndSignalEvent, TaskTraits::Override(GetParam().traits, {TaskPriority::USER_BLOCKING}), Unretained(&task_running))); task_running.Wait(); } // Verify that all tasks posted via PostDelayedTaskWithTraits() after Start() // run in a USER_BLOCKING environment when the AllTasksUserBlocking variation // param of the BrowserScheduler experiment is true. TEST_P(TaskSchedulerImplTest, AllTasksAreUserBlocking) { EnableAllTasksUserBlocking(); StartTaskScheduler(); WaitableEvent task_running; // Ignore |params.execution_mode| in this test. scheduler_.PostDelayedTaskWithTraits( FROM_HERE, GetParam().traits, BindOnce(&VerifyTaskEnvironmentAndSignalEvent, TaskTraits::Override(GetParam().traits, {TaskPriority::USER_BLOCKING}), Unretained(&task_running)), TimeDelta()); task_running.Wait(); } // Verifies that FlushAsyncForTesting() calls back correctly for all trait and // execution mode pairs. TEST_P(TaskSchedulerImplTest, FlushAsyncForTestingSimple) { StartTaskScheduler(); WaitableEvent unblock_task; CreateTaskRunnerWithTraitsAndExecutionMode( &scheduler_, TaskTraits::Override(GetParam().traits, {WithBaseSyncPrimitives()}), GetParam().execution_mode, SingleThreadTaskRunnerThreadMode::DEDICATED) ->PostTask(FROM_HERE, BindOnce(&WaitableEvent::Wait, Unretained(&unblock_task))); WaitableEvent flush_event; scheduler_.FlushAsyncForTesting( BindOnce(&WaitableEvent::Signal, Unretained(&flush_event))); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); EXPECT_FALSE(flush_event.IsSignaled()); unblock_task.Signal(); flush_event.Wait(); } INSTANTIATE_TEST_CASE_P(OneTraitsExecutionModePair, TaskSchedulerImplTest, ::testing::ValuesIn(GetTraitsExecutionModePairs())); // 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_F(TaskSchedulerImplTest, MultipleTraitsExecutionModePairs) { StartTaskScheduler(); std::vector> threads_posting_tasks; for (const auto& traits_execution_mode_pair : GetTraitsExecutionModePairs()) { threads_posting_tasks.push_back(WrapUnique( new ThreadPostingTasks(&scheduler_, traits_execution_mode_pair.traits, traits_execution_mode_pair.execution_mode))); threads_posting_tasks.back()->Start(); } for (const auto& thread : threads_posting_tasks) { thread->WaitForAllTasksToRun(); thread->Join(); } } TEST_F(TaskSchedulerImplTest, GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated) { StartTaskScheduler(); // GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated() does not support // TaskPriority::BACKGROUND. EXPECT_DCHECK_DEATH({ scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {TaskPriority::BACKGROUND}); }); EXPECT_DCHECK_DEATH({ scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {MayBlock(), TaskPriority::BACKGROUND}); }); EXPECT_EQ(4, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {TaskPriority::USER_VISIBLE})); EXPECT_EQ(12, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {MayBlock(), TaskPriority::USER_VISIBLE})); EXPECT_EQ(4, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {TaskPriority::USER_BLOCKING})); EXPECT_EQ(12, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( {MayBlock(), TaskPriority::USER_BLOCKING})); } // Verify that the RunsTasksInCurrentSequence() method of a SequencedTaskRunner // returns false when called from a task that isn't part of the sequence. TEST_F(TaskSchedulerImplTest, SequencedRunsTasksInCurrentSequence) { StartTaskScheduler(); auto single_thread_task_runner = scheduler_.CreateSingleThreadTaskRunnerWithTraits( TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED); auto sequenced_task_runner = scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); WaitableEvent task_ran; single_thread_task_runner->PostTask( FROM_HERE, BindOnce( [](scoped_refptr sequenced_task_runner, WaitableEvent* 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_F(TaskSchedulerImplTest, SingleThreadRunsTasksInCurrentSequence) { StartTaskScheduler(); auto sequenced_task_runner = scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); auto single_thread_task_runner = scheduler_.CreateSingleThreadTaskRunnerWithTraits( TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED); WaitableEvent task_ran; sequenced_task_runner->PostTask( FROM_HERE, BindOnce( [](scoped_refptr single_thread_task_runner, WaitableEvent* task_ran) { EXPECT_FALSE( single_thread_task_runner->RunsTasksInCurrentSequence()); task_ran->Signal(); }, single_thread_task_runner, Unretained(&task_ran))); task_ran.Wait(); } #if defined(OS_WIN) TEST_F(TaskSchedulerImplTest, COMSTATaskRunnersRunWithCOMSTA) { StartTaskScheduler(); auto com_sta_task_runner = scheduler_.CreateCOMSTATaskRunnerWithTraits( TaskTraits(), SingleThreadTaskRunnerThreadMode::SHARED); WaitableEvent task_ran; com_sta_task_runner->PostTask( FROM_HERE, Bind( [](WaitableEvent* task_ran) { win::AssertComApartmentType(win::ComApartmentType::STA); task_ran->Signal(); }, Unretained(&task_ran))); task_ran.Wait(); } #endif // defined(OS_WIN) TEST_F(TaskSchedulerImplTest, DelayedTasksNotRunAfterShutdown) { StartTaskScheduler(); // 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. scheduler_.PostDelayedTaskWithTraits(FROM_HERE, TaskTraits(), BindOnce([]() { ADD_FAILURE(); }), TestTimeouts::tiny_timeout()); scheduler_.Shutdown(); PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 2); } #if defined(OS_POSIX) TEST_F(TaskSchedulerImplTest, FileDescriptorWatcherNoOpsAfterShutdown) { StartTaskScheduler(); int pipes[2]; ASSERT_EQ(0, pipe(pipes)); scoped_refptr blocking_task_runner = scheduler_.CreateSequencedTaskRunnerWithTraits( {TaskShutdownBehavior::BLOCK_SHUTDOWN}); blocking_task_runner->PostTask( FROM_HERE, BindOnce( [](int read_fd) { std::unique_ptr 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 task scheduler 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])); scheduler_.Shutdown(); constexpr char kByte = '!'; ASSERT_TRUE(WriteFileDescriptor(pipes[1], &kByte, sizeof(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 // defined(OS_POSIX) // Verify that tasks posted on the same sequence access the same values on // SequenceLocalStorage, and tasks on different sequences see different values. TEST_F(TaskSchedulerImplTest, SequenceLocalStorage) { StartTaskScheduler(); SequenceLocalStorageSlot slot; auto sequenced_task_runner1 = scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); auto sequenced_task_runner2 = scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); sequenced_task_runner1->PostTask( FROM_HERE, BindOnce([](SequenceLocalStorageSlot* slot) { slot->Set(11); }, &slot)); sequenced_task_runner1->PostTask(FROM_HERE, BindOnce( [](SequenceLocalStorageSlot* slot) { EXPECT_EQ(slot->Get(), 11); }, &slot)); sequenced_task_runner2->PostTask(FROM_HERE, BindOnce( [](SequenceLocalStorageSlot* slot) { EXPECT_NE(slot->Get(), 11); }, &slot)); scheduler_.FlushForTesting(); } TEST_F(TaskSchedulerImplTest, FlushAsyncNoTasks) { StartTaskScheduler(); bool called_back = false; scheduler_.FlushAsyncForTesting( BindOnce([](bool* called_back) { *called_back = true; }, Unretained(&called_back))); EXPECT_TRUE(called_back); } namespace { // Verifies that |query| is found on the current stack. Ignores failures if this // configuration doesn't have symbols. void VerifyHasStringOnStack(const std::string& query) { const std::string stack = debug::StackTrace().ToString(); SCOPED_TRACE(stack); const bool found_on_stack = stack.find(query) != std::string::npos; const bool stack_has_symbols = stack.find("SchedulerWorker") != std::string::npos; EXPECT_TRUE(found_on_stack || !stack_has_symbols) << query; } } // namespace #if defined(OS_POSIX) // Many POSIX bots flakily crash on |debug::StackTrace().ToString()|, // https://crbug.com/840429. #define MAYBE_IdentifiableStacks DISABLED_IdentifiableStacks #elif defined(OS_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 (useful to diagnose issues from // logs without memory dumps). TEST_F(TaskSchedulerImplTest, MAYBE_IdentifiableStacks) { StartTaskScheduler(); scheduler_.CreateSequencedTaskRunnerWithTraits({})->PostTask( FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunPooledWorker")); scheduler_.CreateSequencedTaskRunnerWithTraits({TaskPriority::BACKGROUND}) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunBackgroundPooledWorker")); scheduler_ .CreateSingleThreadTaskRunnerWithTraits( {}, SingleThreadTaskRunnerThreadMode::SHARED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunSharedWorker")); scheduler_ .CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunBackgroundSharedWorker")); scheduler_ .CreateSingleThreadTaskRunnerWithTraits( {}, SingleThreadTaskRunnerThreadMode::DEDICATED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunDedicatedWorker")); scheduler_ .CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunBackgroundDedicatedWorker")); #if defined(OS_WIN) scheduler_ .CreateCOMSTATaskRunnerWithTraits( {}, SingleThreadTaskRunnerThreadMode::SHARED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunSharedCOMWorker")); scheduler_ .CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunBackgroundSharedCOMWorker")); scheduler_ .CreateCOMSTATaskRunnerWithTraits( {}, SingleThreadTaskRunnerThreadMode::DEDICATED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunDedicatedCOMWorker")); scheduler_ .CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED) ->PostTask(FROM_HERE, BindOnce(&VerifyHasStringOnStack, "RunBackgroundDedicatedCOMWorker")); #endif // defined(OS_WIN) scheduler_.FlushForTesting(); } TEST_F(TaskSchedulerImplTest, SchedulerWorkerObserver) { testing::StrictMock observer; set_scheduler_worker_observer(&observer); // A worker should be created for each pool. After that, 8 threads should be // created for single-threaded work (16 on Windows). const int kExpectedNumPoolWorkers = CanUseBackgroundPriorityForSchedulerWorker() ? 4 : 2; #if defined(OS_WIN) const int kExpectedNumSingleThreadedWorkers = 16; #else const int kExpectedNumSingleThreadedWorkers = 8; #endif const int kExpectedNumWorkers = kExpectedNumPoolWorkers + kExpectedNumSingleThreadedWorkers; EXPECT_CALL(observer, OnSchedulerWorkerMainEntry()) .Times(kExpectedNumWorkers); StartTaskScheduler(); std::vector> task_runners; task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND, MayBlock()}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::USER_BLOCKING, MayBlock()}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::BACKGROUND, MayBlock()}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateSingleThreadTaskRunnerWithTraits( {TaskPriority::USER_BLOCKING, MayBlock()}, SingleThreadTaskRunnerThreadMode::DEDICATED)); #if defined(OS_WIN) task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND, MayBlock()}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::USER_BLOCKING, MayBlock()}, SingleThreadTaskRunnerThreadMode::SHARED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::BACKGROUND, MayBlock()}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::USER_BLOCKING}, SingleThreadTaskRunnerThreadMode::DEDICATED)); task_runners.push_back(scheduler_.CreateCOMSTATaskRunnerWithTraits( {TaskPriority::USER_BLOCKING, MayBlock()}, SingleThreadTaskRunnerThreadMode::DEDICATED)); #endif for (auto& task_runner : task_runners) task_runner->PostTask(FROM_HERE, DoNothing()); EXPECT_CALL(observer, OnSchedulerWorkerMainExit()).Times(kExpectedNumWorkers); // Allow single-threaded workers to be released. task_runners.clear(); TearDown(); } } // namespace internal } // namespace base