| // 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 <stddef.h> | 
 |  | 
 | #include <string> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/callback.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/lock.h" | 
 | #include "base/synchronization/waitable_event.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/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 <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  // 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) { | 
 |   const bool supports_background_priority = | 
 |       Lock::HandlesMultipleThreadPriorities() && | 
 |       PlatformThread::CanIncreaseCurrentThreadPriority(); | 
 |  | 
 |   EXPECT_EQ(supports_background_priority && | 
 |                     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")); | 
 |   EXPECT_NE(std::string::npos, | 
 |             current_thread_name.find( | 
 |                 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<TaskRunner> 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<TraitsExecutionModePair> GetTraitsExecutionModePairs() { | 
 |   std::vector<TraitsExecutionModePair> 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<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}, execution_mode)); | 
 |       params.push_back(TraitsExecutionModePair({MayBlock()}, execution_mode)); | 
 |     } | 
 |   } | 
 |  | 
 |   return params; | 
 | } | 
 |  | 
 | class TaskSchedulerImplTest | 
 |     : public testing::TestWithParam<TraitsExecutionModePair> { | 
 |  protected: | 
 |   TaskSchedulerImplTest() : scheduler_("Test"), field_trial_list_(nullptr) {} | 
 |  | 
 |   void EnableAllTasksUserBlocking() { | 
 |     constexpr char kFieldTrialName[] = "BrowserScheduler"; | 
 |     constexpr char kFieldTrialTestGroup[] = "DummyGroup"; | 
 |     std::map<std::string, std::string> 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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   // 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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                              WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   CreateTaskRunnerWithTraitsAndExecutionMode( | 
 |       &scheduler_, | 
 |       TaskTraits::Override(GetParam().traits, {WithBaseSyncPrimitives()}), | 
 |       GetParam().execution_mode, SingleThreadTaskRunnerThreadMode::DEDICATED) | 
 |       ->PostTask(FROM_HERE, | 
 |                  BindOnce(&WaitableEvent::Wait, Unretained(&unblock_task))); | 
 |  | 
 |   WaitableEvent flush_event(WaitableEvent::ResetPolicy::MANUAL, | 
 |                             WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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<std::unique_ptr<ThreadPostingTasks>> 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(); | 
 |   EXPECT_EQ(1, scheduler_.GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated( | 
 |                    {TaskPriority::BACKGROUND})); | 
 |   EXPECT_EQ(3, 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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   single_thread_task_runner->PostTask( | 
 |       FROM_HERE, | 
 |       BindOnce( | 
 |           [](scoped_refptr<TaskRunner> 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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   sequenced_task_runner->PostTask( | 
 |       FROM_HERE, | 
 |       BindOnce( | 
 |           [](scoped_refptr<TaskRunner> 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(WaitableEvent::ResetPolicy::MANUAL, | 
 |                          WaitableEvent::InitialState::NOT_SIGNALED); | 
 |   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<TaskRunner> blocking_task_runner = | 
 |       scheduler_.CreateSequencedTaskRunnerWithTraits( | 
 |           {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 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<int> slot; | 
 |   auto sequenced_task_runner1 = | 
 |       scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); | 
 |   auto sequenced_task_runner2 = | 
 |       scheduler_.CreateSequencedTaskRunnerWithTraits(TaskTraits()); | 
 |  | 
 |   sequenced_task_runner1->PostTask( | 
 |       FROM_HERE, | 
 |       BindOnce([](SequenceLocalStorageSlot<int>* slot) { slot->Set(11); }, | 
 |                &slot)); | 
 |  | 
 |   sequenced_task_runner1->PostTask(FROM_HERE, | 
 |                                    BindOnce( | 
 |                                        [](SequenceLocalStorageSlot<int>* slot) { | 
 |                                          EXPECT_EQ(slot->Get(), 11); | 
 |                                        }, | 
 |                                        &slot)); | 
 |  | 
 |   sequenced_task_runner2->PostTask(FROM_HERE, | 
 |                                    BindOnce( | 
 |                                        [](SequenceLocalStorageSlot<int>* 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 | 
 | #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<test::MockSchedulerWorkerObserver> observer; | 
 |   set_scheduler_worker_observer(&observer); | 
 |  | 
 | // 4 workers should be created for the 4 pools. After that, 8 threads should | 
 | // be created for single-threaded work (16 on Windows). | 
 | #if defined(OS_WIN) | 
 |   constexpr int kExpectedNumWorkers = 20; | 
 | #else | 
 |   constexpr int kExpectedNumWorkers = 12; | 
 | #endif | 
 |   EXPECT_CALL(observer, OnSchedulerWorkerMainEntry()) | 
 |       .Times(kExpectedNumWorkers); | 
 |  | 
 |   StartTaskScheduler(); | 
 |  | 
 |   std::vector<scoped_refptr<SingleThreadTaskRunner>> 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 |