| // 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/task_scheduler/scheduler_worker.h" | 
 |  | 
 | #include <memory> | 
 | #include <vector> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/metrics/statistics_recorder.h" | 
 | #include "base/synchronization/condition_variable.h" | 
 | #include "base/synchronization/waitable_event.h" | 
 | #include "base/task/task_scheduler/environment_config.h" | 
 | #include "base/task/task_scheduler/scheduler_lock.h" | 
 | #include "base/task/task_scheduler/scheduler_worker_observer.h" | 
 | #include "base/task/task_scheduler/sequence.h" | 
 | #include "base/task/task_scheduler/task.h" | 
 | #include "base/task/task_scheduler/task_tracker.h" | 
 | #include "base/task/task_scheduler/test_utils.h" | 
 | #include "base/test/test_timeouts.h" | 
 | #include "base/threading/platform_thread.h" | 
 | #include "base/threading/simple_thread.h" | 
 | #include "base/time/time.h" | 
 | #include "build/build_config.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | #if defined(OS_WIN) | 
 | #include <objbase.h> | 
 |  | 
 | #include "base/win/com_init_check_hook.h" | 
 | #include "starboard/types.h" | 
 | #endif | 
 |  | 
 | using testing::_; | 
 | using testing::Mock; | 
 | using testing::Ne; | 
 | using testing::StrictMock; | 
 |  | 
 | namespace base { | 
 | namespace internal { | 
 | namespace { | 
 |  | 
 | const size_t kNumSequencesPerTest = 150; | 
 |  | 
 | class SchedulerWorkerDefaultDelegate : public SchedulerWorker::Delegate { | 
 |  public: | 
 |   SchedulerWorkerDefaultDelegate() = default; | 
 |  | 
 |   // SchedulerWorker::Delegate: | 
 |   void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override { | 
 |     ADD_FAILURE() << "Unexpected call to OnCanScheduleSequence()."; | 
 |   } | 
 |   SchedulerWorker::ThreadLabel GetThreadLabel() const override { | 
 |     return SchedulerWorker::ThreadLabel::DEDICATED; | 
 |   } | 
 |   void OnMainEntry(const SchedulerWorker* worker) override {} | 
 |   scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
 |     return nullptr; | 
 |   } | 
 |   void DidRunTask() override { | 
 |     ADD_FAILURE() << "Unexpected call to DidRunTask()"; | 
 |   } | 
 |   void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override { | 
 |     ADD_FAILURE() << "Unexpected call to ReEnqueueSequence()"; | 
 |   } | 
 |   TimeDelta GetSleepTimeout() override { return TimeDelta::Max(); } | 
 |  | 
 |  private: | 
 |   DISALLOW_COPY_AND_ASSIGN(SchedulerWorkerDefaultDelegate); | 
 | }; | 
 |  | 
 | // The test parameter is the number of Tasks per Sequence returned by GetWork(). | 
 | class TaskSchedulerWorkerTest : public testing::TestWithParam<size_t> { | 
 |  protected: | 
 |   TaskSchedulerWorkerTest() | 
 |       : recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()), | 
 |         num_get_work_cv_(lock_.CreateConditionVariable()) {} | 
 |  | 
 |   void SetUp() override { | 
 |     worker_ = MakeRefCounted<SchedulerWorker>( | 
 |         ThreadPriority::NORMAL, | 
 |         std::make_unique<TestSchedulerWorkerDelegate>(this), | 
 |         task_tracker_.GetTrackedRef()); | 
 |     ASSERT_TRUE(worker_); | 
 |     worker_->Start(); | 
 |     worker_set_.Signal(); | 
 |     main_entry_called_.Wait(); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     // |worker_| needs to be released before ~TaskTracker() as it holds a | 
 |     // TrackedRef to it. | 
 |     worker_->JoinForTesting(); | 
 |     worker_ = nullptr; | 
 |   } | 
 |  | 
 |   size_t TasksPerSequence() const { return GetParam(); } | 
 |  | 
 |   // Wait until GetWork() has been called |num_get_work| times. | 
 |   void WaitForNumGetWork(size_t num_get_work) { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     while (num_get_work_ < num_get_work) | 
 |       num_get_work_cv_->Wait(); | 
 |   } | 
 |  | 
 |   void SetMaxGetWork(size_t max_get_work) { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     max_get_work_ = max_get_work; | 
 |   } | 
 |  | 
 |   void SetNumSequencesToCreate(size_t num_sequences_to_create) { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     EXPECT_EQ(0U, num_sequences_to_create_); | 
 |     num_sequences_to_create_ = num_sequences_to_create; | 
 |   } | 
 |  | 
 |   size_t NumRunTasks() { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     return num_run_tasks_; | 
 |   } | 
 |  | 
 |   std::vector<scoped_refptr<Sequence>> CreatedSequences() { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     return created_sequences_; | 
 |   } | 
 |  | 
 |   std::vector<scoped_refptr<Sequence>> EnqueuedSequences() { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     return re_enqueued_sequences_; | 
 |   } | 
 |  | 
 |   scoped_refptr<SchedulerWorker> worker_; | 
 |  | 
 |  private: | 
 |   class TestSchedulerWorkerDelegate : public SchedulerWorkerDefaultDelegate { | 
 |    public: | 
 |     TestSchedulerWorkerDelegate(TaskSchedulerWorkerTest* outer) | 
 |         : outer_(outer) {} | 
 |  | 
 |     ~TestSchedulerWorkerDelegate() override { | 
 |       EXPECT_FALSE(IsCallToDidRunTaskExpected()); | 
 |     } | 
 |  | 
 |     // SchedulerWorker::Delegate: | 
 |     void OnMainEntry(const SchedulerWorker* worker) override { | 
 |       outer_->worker_set_.Wait(); | 
 |       EXPECT_EQ(outer_->worker_.get(), worker); | 
 |       EXPECT_FALSE(IsCallToDidRunTaskExpected()); | 
 |  | 
 |       // Without synchronization, OnMainEntry() could be called twice without | 
 |       // generating an error. | 
 |       AutoSchedulerLock auto_lock(outer_->lock_); | 
 |       EXPECT_FALSE(outer_->main_entry_called_.IsSignaled()); | 
 |       outer_->main_entry_called_.Signal(); | 
 |     } | 
 |  | 
 |     scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
 |       EXPECT_FALSE(IsCallToDidRunTaskExpected()); | 
 |       EXPECT_EQ(outer_->worker_.get(), worker); | 
 |  | 
 |       { | 
 |         AutoSchedulerLock auto_lock(outer_->lock_); | 
 |  | 
 |         // Increment the number of times that this method has been called. | 
 |         ++outer_->num_get_work_; | 
 |         outer_->num_get_work_cv_->Signal(); | 
 |  | 
 |         // Verify that this method isn't called more times than expected. | 
 |         EXPECT_LE(outer_->num_get_work_, outer_->max_get_work_); | 
 |  | 
 |         // Check if a Sequence should be returned. | 
 |         if (outer_->num_sequences_to_create_ == 0) | 
 |           return nullptr; | 
 |         --outer_->num_sequences_to_create_; | 
 |       } | 
 |  | 
 |       // Create a Sequence with TasksPerSequence() Tasks. | 
 |       scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(TaskTraits()); | 
 |       for (size_t i = 0; i < outer_->TasksPerSequence(); ++i) { | 
 |         Task task(FROM_HERE, | 
 |                   BindOnce(&TaskSchedulerWorkerTest::RunTaskCallback, | 
 |                            Unretained(outer_)), | 
 |                   TimeDelta()); | 
 |         EXPECT_TRUE(outer_->task_tracker_.WillPostTask( | 
 |             &task, sequence->traits().shutdown_behavior())); | 
 |         sequence->PushTask(std::move(task)); | 
 |       } | 
 |  | 
 |       ExpectCallToDidRunTask(); | 
 |  | 
 |       { | 
 |         // Add the Sequence to the vector of created Sequences. | 
 |         AutoSchedulerLock auto_lock(outer_->lock_); | 
 |         outer_->created_sequences_.push_back(sequence); | 
 |       } | 
 |  | 
 |       sequence = outer_->task_tracker_.WillScheduleSequence(std::move(sequence), | 
 |                                                             nullptr); | 
 |       EXPECT_TRUE(sequence); | 
 |       return sequence; | 
 |     } | 
 |  | 
 |     void DidRunTask() override { | 
 |       AutoSchedulerLock auto_lock(expect_did_run_task_lock_); | 
 |       EXPECT_TRUE(expect_did_run_task_); | 
 |       expect_did_run_task_ = false; | 
 |     } | 
 |  | 
 |     // This override verifies that |sequence| contains the expected number of | 
 |     // Tasks and adds it to |enqueued_sequences_|. Unlike a normal | 
 |     // EnqueueSequence implementation, it doesn't reinsert |sequence| into a | 
 |     // queue for further execution. | 
 |     void ReEnqueueSequence(scoped_refptr<Sequence> sequence) override { | 
 |       EXPECT_FALSE(IsCallToDidRunTaskExpected()); | 
 |       EXPECT_GT(outer_->TasksPerSequence(), 1U); | 
 |  | 
 |       // Verify that |sequence| contains TasksPerSequence() - 1 Tasks. | 
 |       for (size_t i = 0; i < outer_->TasksPerSequence() - 1; ++i) { | 
 |         EXPECT_TRUE(sequence->TakeTask()); | 
 |         EXPECT_EQ(i == outer_->TasksPerSequence() - 2, sequence->Pop()); | 
 |       } | 
 |  | 
 |       // Add |sequence| to |re_enqueued_sequences_|. | 
 |       AutoSchedulerLock auto_lock(outer_->lock_); | 
 |       outer_->re_enqueued_sequences_.push_back(std::move(sequence)); | 
 |       EXPECT_LE(outer_->re_enqueued_sequences_.size(), | 
 |                 outer_->created_sequences_.size()); | 
 |     } | 
 |  | 
 |    private: | 
 |     // Expect a call to DidRunTask() before the next call to any other method of | 
 |     // this delegate. | 
 |     void ExpectCallToDidRunTask() { | 
 |       AutoSchedulerLock auto_lock(expect_did_run_task_lock_); | 
 |       expect_did_run_task_ = true; | 
 |     } | 
 |  | 
 |     bool IsCallToDidRunTaskExpected() const { | 
 |       AutoSchedulerLock auto_lock(expect_did_run_task_lock_); | 
 |       return expect_did_run_task_; | 
 |     } | 
 |  | 
 |     TaskSchedulerWorkerTest* outer_; | 
 |  | 
 |     // Synchronizes access to |expect_did_run_task_|. | 
 |     mutable SchedulerLock expect_did_run_task_lock_; | 
 |  | 
 |     // Whether the next method called on this delegate should be DidRunTask(). | 
 |     bool expect_did_run_task_ = false; | 
 |  | 
 |     DISALLOW_COPY_AND_ASSIGN(TestSchedulerWorkerDelegate); | 
 |   }; | 
 |  | 
 |   void RunTaskCallback() { | 
 |     AutoSchedulerLock auto_lock(lock_); | 
 |     ++num_run_tasks_; | 
 |     EXPECT_LE(num_run_tasks_, created_sequences_.size()); | 
 |   } | 
 |  | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing_; | 
 |  | 
 |   TaskTracker task_tracker_ = {"Test"}; | 
 |  | 
 |   // Synchronizes access to all members below. | 
 |   mutable SchedulerLock lock_; | 
 |  | 
 |   // Signaled once OnMainEntry() has been called. | 
 |   WaitableEvent main_entry_called_; | 
 |  | 
 |   // Number of Sequences that should be created by GetWork(). When this | 
 |   // is 0, GetWork() returns nullptr. | 
 |   size_t num_sequences_to_create_ = 0; | 
 |  | 
 |   // Number of times that GetWork() has been called. | 
 |   size_t num_get_work_ = 0; | 
 |  | 
 |   // Maximum number of times that GetWork() can be called. | 
 |   size_t max_get_work_ = 0; | 
 |  | 
 |   // Condition variable signaled when |num_get_work_| is incremented. | 
 |   std::unique_ptr<ConditionVariable> num_get_work_cv_; | 
 |  | 
 |   // Sequences created by GetWork(). | 
 |   std::vector<scoped_refptr<Sequence>> created_sequences_; | 
 |  | 
 |   // Sequences passed to EnqueueSequence(). | 
 |   std::vector<scoped_refptr<Sequence>> re_enqueued_sequences_; | 
 |  | 
 |   // Number of times that RunTaskCallback() has been called. | 
 |   size_t num_run_tasks_ = 0; | 
 |  | 
 |   // Signaled after |worker_| is set. | 
 |   WaitableEvent worker_set_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerTest); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Verify that when GetWork() continuously returns Sequences, all Tasks in these | 
 | // Sequences run successfully. The test wakes up the SchedulerWorker once. | 
 | TEST_P(TaskSchedulerWorkerTest, ContinuousWork) { | 
 |   // Set GetWork() to return |kNumSequencesPerTest| Sequences before starting to | 
 |   // return nullptr. | 
 |   SetNumSequencesToCreate(kNumSequencesPerTest); | 
 |  | 
 |   // Expect |kNumSequencesPerTest| calls to GetWork() in which it returns a | 
 |   // Sequence and one call in which its returns nullptr. | 
 |   const size_t kExpectedNumGetWork = kNumSequencesPerTest + 1; | 
 |   SetMaxGetWork(kExpectedNumGetWork); | 
 |  | 
 |   // Wake up |worker_| and wait until GetWork() has been invoked the | 
 |   // expected amount of times. | 
 |   worker_->WakeUp(); | 
 |   WaitForNumGetWork(kExpectedNumGetWork); | 
 |  | 
 |   // All tasks should have run. | 
 |   EXPECT_EQ(kNumSequencesPerTest, NumRunTasks()); | 
 |  | 
 |   // If Sequences returned by GetWork() contain more than one Task, they aren't | 
 |   // empty after the worker pops Tasks from them and thus should be returned to | 
 |   // EnqueueSequence(). | 
 |   if (TasksPerSequence() > 1) | 
 |     EXPECT_EQ(CreatedSequences(), EnqueuedSequences()); | 
 |   else | 
 |     EXPECT_TRUE(EnqueuedSequences().empty()); | 
 | } | 
 |  | 
 | // Verify that when GetWork() alternates between returning a Sequence and | 
 | // returning nullptr, all Tasks in the returned Sequences run successfully. The | 
 | // test wakes up the SchedulerWorker once for each Sequence. | 
 | TEST_P(TaskSchedulerWorkerTest, IntermittentWork) { | 
 |   for (size_t i = 0; i < kNumSequencesPerTest; ++i) { | 
 |     // Set GetWork() to return 1 Sequence before starting to return | 
 |     // nullptr. | 
 |     SetNumSequencesToCreate(1); | 
 |  | 
 |     // Expect |i + 1| calls to GetWork() in which it returns a Sequence and | 
 |     // |i + 1| calls in which it returns nullptr. | 
 |     const size_t expected_num_get_work = 2 * (i + 1); | 
 |     SetMaxGetWork(expected_num_get_work); | 
 |  | 
 |     // Wake up |worker_| and wait until GetWork() has been invoked | 
 |     // the expected amount of times. | 
 |     worker_->WakeUp(); | 
 |     WaitForNumGetWork(expected_num_get_work); | 
 |  | 
 |     // The Task should have run | 
 |     EXPECT_EQ(i + 1, NumRunTasks()); | 
 |  | 
 |     // If Sequences returned by GetWork() contain more than one Task, they | 
 |     // aren't empty after the worker pops Tasks from them and thus should be | 
 |     // returned to EnqueueSequence(). | 
 |     if (TasksPerSequence() > 1) | 
 |       EXPECT_EQ(CreatedSequences(), EnqueuedSequences()); | 
 |     else | 
 |       EXPECT_TRUE(EnqueuedSequences().empty()); | 
 |   } | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_CASE_P(OneTaskPerSequence, | 
 |                         TaskSchedulerWorkerTest, | 
 |                         ::testing::Values(1)); | 
 | INSTANTIATE_TEST_CASE_P(TwoTasksPerSequence, | 
 |                         TaskSchedulerWorkerTest, | 
 |                         ::testing::Values(2)); | 
 |  | 
 | namespace { | 
 |  | 
 | class ControllableCleanupDelegate : public SchedulerWorkerDefaultDelegate { | 
 |  public: | 
 |   class Controls : public RefCountedThreadSafe<Controls> { | 
 |    public: | 
 |     Controls() = default; | 
 |  | 
 |     void HaveWorkBlock() { work_running_.Reset(); } | 
 |  | 
 |     void UnblockWork() { work_running_.Signal(); } | 
 |  | 
 |     void WaitForWorkToRun() { work_processed_.Wait(); } | 
 |  | 
 |     void WaitForCleanupRequest() { cleanup_requested_.Wait(); } | 
 |  | 
 |     void WaitForDelegateDestroy() { destroyed_.Wait(); } | 
 |  | 
 |     void WaitForMainExit() { exited_.Wait(); } | 
 |  | 
 |     void set_expect_get_work(bool expect_get_work) { | 
 |       expect_get_work_ = expect_get_work; | 
 |     } | 
 |  | 
 |     void ResetState() { | 
 |       work_running_.Signal(); | 
 |       work_processed_.Reset(); | 
 |       cleanup_requested_.Reset(); | 
 |       exited_.Reset(); | 
 |       work_requested_ = false; | 
 |     } | 
 |  | 
 |     void set_can_cleanup(bool can_cleanup) { can_cleanup_ = can_cleanup; } | 
 |  | 
 |    private: | 
 |     friend class ControllableCleanupDelegate; | 
 |     friend class RefCountedThreadSafe<Controls>; | 
 |     ~Controls() = default; | 
 |  | 
 |     WaitableEvent work_running_{WaitableEvent::ResetPolicy::MANUAL, | 
 |                                 WaitableEvent::InitialState::SIGNALED}; | 
 |     WaitableEvent work_processed_; | 
 |     WaitableEvent cleanup_requested_; | 
 |     WaitableEvent destroyed_; | 
 |     WaitableEvent exited_; | 
 |  | 
 |     bool expect_get_work_ = true; | 
 |     bool can_cleanup_ = false; | 
 |     bool work_requested_ = false; | 
 |  | 
 |     DISALLOW_COPY_AND_ASSIGN(Controls); | 
 |   }; | 
 |  | 
 |   ControllableCleanupDelegate(TaskTracker* task_tracker) | 
 |       : task_tracker_(task_tracker), controls_(new Controls()) {} | 
 |  | 
 |   ~ControllableCleanupDelegate() override { controls_->destroyed_.Signal(); } | 
 |  | 
 |   scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
 |     EXPECT_TRUE(controls_->expect_get_work_); | 
 |  | 
 |     // Sends one item of work to signal |work_processed_|. On subsequent calls, | 
 |     // sends nullptr to indicate there's no more work to be done. | 
 |     if (controls_->work_requested_) { | 
 |       if (CanCleanup(worker)) { | 
 |         OnCleanup(); | 
 |         worker->Cleanup(); | 
 |         controls_->set_expect_get_work(false); | 
 |       } | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     controls_->work_requested_ = true; | 
 |     scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(TaskTraits( | 
 |         WithBaseSyncPrimitives(), TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)); | 
 |     Task task( | 
 |         FROM_HERE, | 
 |         BindOnce( | 
 |             [](WaitableEvent* work_processed, WaitableEvent* work_running) { | 
 |               work_processed->Signal(); | 
 |               work_running->Wait(); | 
 |             }, | 
 |             Unretained(&controls_->work_processed_), | 
 |             Unretained(&controls_->work_running_)), | 
 |         TimeDelta()); | 
 |     EXPECT_TRUE(task_tracker_->WillPostTask( | 
 |         &task, sequence->traits().shutdown_behavior())); | 
 |     sequence->PushTask(std::move(task)); | 
 |     sequence = | 
 |         task_tracker_->WillScheduleSequence(std::move(sequence), nullptr); | 
 |     EXPECT_TRUE(sequence); | 
 |     return sequence; | 
 |   } | 
 |  | 
 |   void DidRunTask() override {} | 
 |  | 
 |   void OnMainExit(SchedulerWorker* worker) override { | 
 |     controls_->exited_.Signal(); | 
 |   } | 
 |  | 
 |   bool CanCleanup(SchedulerWorker* worker) { | 
 |     // Saving |can_cleanup_| now so that callers waiting on |cleanup_requested_| | 
 |     // have the thread go to sleep and then allow timing out. | 
 |     bool can_cleanup = controls_->can_cleanup_; | 
 |     controls_->cleanup_requested_.Signal(); | 
 |     return can_cleanup; | 
 |   } | 
 |  | 
 |   void OnCleanup() { | 
 |     EXPECT_TRUE(controls_->can_cleanup_); | 
 |     EXPECT_TRUE(controls_->cleanup_requested_.IsSignaled()); | 
 |   } | 
 |  | 
 |   // ControllableCleanupDelegate: | 
 |   scoped_refptr<Controls> controls() { return controls_; } | 
 |  | 
 |  private: | 
 |   scoped_refptr<Sequence> work_sequence_; | 
 |   TaskTracker* const task_tracker_; | 
 |   scoped_refptr<Controls> controls_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(ControllableCleanupDelegate); | 
 | }; | 
 |  | 
 | class MockedControllableCleanupDelegate : public ControllableCleanupDelegate { | 
 |  public: | 
 |   MockedControllableCleanupDelegate(TaskTracker* task_tracker) | 
 |       : ControllableCleanupDelegate(task_tracker){}; | 
 |   ~MockedControllableCleanupDelegate() override = default; | 
 |  | 
 |   // SchedulerWorker::Delegate: | 
 |   MOCK_METHOD1(OnMainEntry, void(const SchedulerWorker* worker)); | 
 |  | 
 |  private: | 
 |   DISALLOW_COPY_AND_ASSIGN(MockedControllableCleanupDelegate); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Verify that calling SchedulerWorker::Cleanup() from GetWork() causes | 
 | // the SchedulerWorker's thread to exit. | 
 | TEST(TaskSchedulerWorkerTest, WorkerCleanupFromGetWork) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   MockedControllableCleanupDelegate* delegate = | 
 |       new StrictMock<MockedControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |   controls->set_can_cleanup(true); | 
 |   EXPECT_CALL(*delegate, OnMainEntry(_)); | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 WrapUnique(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |   controls->WaitForWorkToRun(); | 
 |   Mock::VerifyAndClear(delegate); | 
 |   controls->WaitForMainExit(); | 
 | } | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWork) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   // No mock here as that's reasonably covered by other tests and the delegate | 
 |   // may destroy on a different thread. Mocks aren't designed with that in mind. | 
 |   std::unique_ptr<ControllableCleanupDelegate> delegate = | 
 |       std::make_unique<ControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |  | 
 |   controls->HaveWorkBlock(); | 
 |  | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |  | 
 |   controls->WaitForWorkToRun(); | 
 |   worker->Cleanup(); | 
 |   worker = nullptr; | 
 |   controls->UnblockWork(); | 
 |   controls->WaitForDelegateDestroy(); | 
 | } | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWait) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   // No mock here as that's reasonably covered by other tests and the delegate | 
 |   // may destroy on a different thread. Mocks aren't designed with that in mind. | 
 |   std::unique_ptr<ControllableCleanupDelegate> delegate = | 
 |       std::make_unique<ControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |  | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |  | 
 |   controls->WaitForCleanupRequest(); | 
 |   worker->Cleanup(); | 
 |   worker = nullptr; | 
 |   controls->WaitForDelegateDestroy(); | 
 | } | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringShutdown) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   // No mock here as that's reasonably covered by other tests and the delegate | 
 |   // may destroy on a different thread. Mocks aren't designed with that in mind. | 
 |   std::unique_ptr<ControllableCleanupDelegate> delegate = | 
 |       std::make_unique<ControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |  | 
 |   controls->HaveWorkBlock(); | 
 |  | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |  | 
 |   controls->WaitForWorkToRun(); | 
 |   task_tracker.Shutdown(); | 
 |   worker->Cleanup(); | 
 |   worker = nullptr; | 
 |   controls->UnblockWork(); | 
 |   controls->WaitForDelegateDestroy(); | 
 | } | 
 |  | 
 | // Verify that Start() is a no-op after Cleanup(). | 
 | TEST(TaskSchedulerWorkerTest, CleanupBeforeStart) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   // No mock here as that's reasonably covered by other tests and the delegate | 
 |   // may destroy on a different thread. Mocks aren't designed with that in mind. | 
 |   std::unique_ptr<ControllableCleanupDelegate> delegate = | 
 |       std::make_unique<ControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |   controls->set_expect_get_work(false); | 
 |  | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |  | 
 |   worker->Cleanup(); | 
 |   worker->Start(); | 
 |  | 
 |   EXPECT_FALSE(worker->ThreadAliveForTesting()); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | class CallJoinFromDifferentThread : public SimpleThread { | 
 |  public: | 
 |   CallJoinFromDifferentThread(SchedulerWorker* worker_to_join) | 
 |       : SimpleThread("SchedulerWorkerJoinThread"), | 
 |         worker_to_join_(worker_to_join) {} | 
 |  | 
 |   ~CallJoinFromDifferentThread() override = default; | 
 |  | 
 |   void Run() override { | 
 |     run_started_event_.Signal(); | 
 |     worker_to_join_->JoinForTesting(); | 
 |   } | 
 |  | 
 |   void WaitForRunToStart() { run_started_event_.Wait(); } | 
 |  | 
 |  private: | 
 |   SchedulerWorker* const worker_to_join_; | 
 |   WaitableEvent run_started_event_; | 
 |   DISALLOW_COPY_AND_ASSIGN(CallJoinFromDifferentThread); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringJoin) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   // Will be owned by SchedulerWorker. | 
 |   // No mock here as that's reasonably covered by other tests and the | 
 |   // delegate may destroy on a different thread. Mocks aren't designed with that | 
 |   // in mind. | 
 |   std::unique_ptr<ControllableCleanupDelegate> delegate = | 
 |       std::make_unique<ControllableCleanupDelegate>(&task_tracker); | 
 |   scoped_refptr<ControllableCleanupDelegate::Controls> controls = | 
 |       delegate->controls(); | 
 |  | 
 |   controls->HaveWorkBlock(); | 
 |  | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |  | 
 |   controls->WaitForWorkToRun(); | 
 |   CallJoinFromDifferentThread join_from_different_thread(worker.get()); | 
 |   join_from_different_thread.Start(); | 
 |   join_from_different_thread.WaitForRunToStart(); | 
 |   // Sleep here to give the other thread a chance to call JoinForTesting(). | 
 |   // Receiving a signal that Run() was called doesn't mean JoinForTesting() was | 
 |   // necessarily called, and we can't signal after JoinForTesting() as | 
 |   // JoinForTesting() blocks until we call UnblockWork(). | 
 |   PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | 
 |   worker->Cleanup(); | 
 |   worker = nullptr; | 
 |   controls->UnblockWork(); | 
 |   controls->WaitForDelegateDestroy(); | 
 |   join_from_different_thread.Join(); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | class ExpectThreadPriorityDelegate : public SchedulerWorkerDefaultDelegate { | 
 |  public: | 
 |   ExpectThreadPriorityDelegate() | 
 |       : priority_verified_in_get_work_event_( | 
 |             WaitableEvent::ResetPolicy::AUTOMATIC, | 
 |             WaitableEvent::InitialState::NOT_SIGNALED), | 
 |         expected_thread_priority_(ThreadPriority::BACKGROUND) {} | 
 |  | 
 |   void SetExpectedThreadPriority(ThreadPriority expected_thread_priority) { | 
 |     expected_thread_priority_ = expected_thread_priority; | 
 |   } | 
 |  | 
 |   void WaitForPriorityVerifiedInGetWork() { | 
 |     priority_verified_in_get_work_event_.Wait(); | 
 |   } | 
 |  | 
 |   // SchedulerWorker::Delegate: | 
 |   void OnMainEntry(const SchedulerWorker* worker) override { | 
 |     VerifyThreadPriority(); | 
 |   } | 
 |   scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
 |     VerifyThreadPriority(); | 
 |     priority_verified_in_get_work_event_.Signal(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |  private: | 
 |   void VerifyThreadPriority() { | 
 |     AutoSchedulerLock auto_lock(expected_thread_priority_lock_); | 
 |     EXPECT_EQ(expected_thread_priority_, | 
 |               PlatformThread::GetCurrentThreadPriority()); | 
 |   } | 
 |  | 
 |   // Signaled after GetWork() has verified the priority of the worker thread. | 
 |   WaitableEvent priority_verified_in_get_work_event_; | 
 |  | 
 |   // Synchronizes access to |expected_thread_priority_|. | 
 |   SchedulerLock expected_thread_priority_lock_; | 
 |  | 
 |   // Expected thread priority for the next call to OnMainEntry() or GetWork(). | 
 |   ThreadPriority expected_thread_priority_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(ExpectThreadPriorityDelegate); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, BumpPriorityOfAliveThreadDuringShutdown) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   if (!CanUseBackgroundPriorityForSchedulerWorker()) | 
 |     return; | 
 |  | 
 |   TaskTracker task_tracker("Test"); | 
 |  | 
 |   std::unique_ptr<ExpectThreadPriorityDelegate> delegate( | 
 |       new ExpectThreadPriorityDelegate); | 
 |   ExpectThreadPriorityDelegate* delegate_raw = delegate.get(); | 
 |   delegate_raw->SetExpectedThreadPriority(ThreadPriority::BACKGROUND); | 
 |   auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::BACKGROUND, | 
 |                                                 std::move(delegate), | 
 |                                                 task_tracker.GetTrackedRef()); | 
 |   worker->Start(); | 
 |  | 
 |   // Verify that the initial thread priority is BACKGROUND (or NORMAL if thread | 
 |   // priority can't be increased). | 
 |   worker->WakeUp(); | 
 |   delegate_raw->WaitForPriorityVerifiedInGetWork(); | 
 |  | 
 |   // Verify that the thread priority is bumped to NORMAL during shutdown. | 
 |   delegate_raw->SetExpectedThreadPriority(ThreadPriority::NORMAL); | 
 |   task_tracker.SetHasShutdownStartedForTesting(); | 
 |   worker->WakeUp(); | 
 |   delegate_raw->WaitForPriorityVerifiedInGetWork(); | 
 |  | 
 |   worker->JoinForTesting(); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | class VerifyCallsToObserverDelegate : public SchedulerWorkerDefaultDelegate { | 
 |  public: | 
 |   VerifyCallsToObserverDelegate(test::MockSchedulerWorkerObserver* observer) | 
 |       : observer_(observer) {} | 
 |  | 
 |   // SchedulerWorker::Delegate: | 
 |   void OnMainEntry(const SchedulerWorker* worker) override { | 
 |     Mock::VerifyAndClear(observer_); | 
 |   } | 
 |  | 
 |   void OnMainExit(SchedulerWorker* worker) override { | 
 |     observer_->AllowCallsOnMainExit(1); | 
 |   } | 
 |  | 
 |  private: | 
 |   test::MockSchedulerWorkerObserver* const observer_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(VerifyCallsToObserverDelegate); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Flaky: crbug.com/846121 | 
 | #if defined(OS_LINUX) && defined(ADDRESS_SANITIZER) | 
 | #define MAYBE_SchedulerWorkerObserver DISABLED_SchedulerWorkerObserver | 
 | #else | 
 | #define MAYBE_SchedulerWorkerObserver SchedulerWorkerObserver | 
 | #endif | 
 |  | 
 | // Verify that the SchedulerWorkerObserver is notified when the worker enters | 
 | // and exits its main function. | 
 | TEST(TaskSchedulerWorkerTest, MAYBE_SchedulerWorkerObserver) { | 
 |   StrictMock<test::MockSchedulerWorkerObserver> observer; | 
 |   { | 
 |     std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |         StatisticsRecorder::CreateTemporaryForTesting(); | 
 |     TaskTracker task_tracker("Test"); | 
 |     auto delegate = std::make_unique<VerifyCallsToObserverDelegate>(&observer); | 
 |     auto worker = MakeRefCounted<SchedulerWorker>(ThreadPriority::NORMAL, | 
 |                                                   std::move(delegate), | 
 |                                                   task_tracker.GetTrackedRef()); | 
 |  | 
 |     EXPECT_CALL(observer, OnSchedulerWorkerMainEntry()); | 
 |     worker->Start(&observer); | 
 |     worker->Cleanup(); | 
 |     worker = nullptr; | 
 |   } | 
 |   Mock::VerifyAndClear(&observer); | 
 | } | 
 |  | 
 | #if defined(OS_WIN) | 
 |  | 
 | namespace { | 
 |  | 
 | class CoInitializeDelegate : public SchedulerWorkerDefaultDelegate { | 
 |  public: | 
 |   CoInitializeDelegate() = default; | 
 |  | 
 |   scoped_refptr<Sequence> GetWork(SchedulerWorker* worker) override { | 
 |     EXPECT_FALSE(get_work_returned_.IsSignaled()); | 
 |     EXPECT_EQ(E_UNEXPECTED, coinitialize_hresult_); | 
 |  | 
 |     coinitialize_hresult_ = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); | 
 |     if (SUCCEEDED(coinitialize_hresult_)) | 
 |       CoUninitialize(); | 
 |  | 
 |     get_work_returned_.Signal(); | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   void WaitUntilGetWorkReturned() { get_work_returned_.Wait(); } | 
 |  | 
 |   HRESULT coinitialize_hresult() const { return coinitialize_hresult_; } | 
 |  | 
 |  private: | 
 |   WaitableEvent get_work_returned_; | 
 |   HRESULT coinitialize_hresult_ = E_UNEXPECTED; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(CoInitializeDelegate); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, BackwardCompatibilityEnabled) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   auto delegate = std::make_unique<CoInitializeDelegate>(); | 
 |   CoInitializeDelegate* const delegate_raw = delegate.get(); | 
 |  | 
 |   // Create a worker with backward compatibility ENABLED. Wake it up and wait | 
 |   // until GetWork() returns. | 
 |   auto worker = MakeRefCounted<SchedulerWorker>( | 
 |       ThreadPriority::NORMAL, std::move(delegate), task_tracker.GetTrackedRef(), | 
 |       nullptr, SchedulerBackwardCompatibility::INIT_COM_STA); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |   delegate_raw->WaitUntilGetWorkReturned(); | 
 |  | 
 | // The call to CoInitializeEx() should have returned S_FALSE to indicate that | 
 | // the COM library was already initialized on the thread. | 
 | // See SchedulerWorker::Thread::ThreadMain for why we expect two different | 
 | // results here. | 
 | #if defined(COM_INIT_CHECK_HOOK_ENABLED) | 
 |   EXPECT_EQ(S_OK, delegate_raw->coinitialize_hresult()); | 
 | #else | 
 |   EXPECT_EQ(S_FALSE, delegate_raw->coinitialize_hresult()); | 
 | #endif | 
 |  | 
 |   worker->JoinForTesting(); | 
 | } | 
 |  | 
 | TEST(TaskSchedulerWorkerTest, BackwardCompatibilityDisabled) { | 
 |   std::unique_ptr<StatisticsRecorder> recorder_for_testing = | 
 |       StatisticsRecorder::CreateTemporaryForTesting(); | 
 |   TaskTracker task_tracker("Test"); | 
 |   auto delegate = std::make_unique<CoInitializeDelegate>(); | 
 |   CoInitializeDelegate* const delegate_raw = delegate.get(); | 
 |  | 
 |   // Create a worker with backward compatibility DISABLED. Wake it up and wait | 
 |   // until GetWork() returns. | 
 |   auto worker = MakeRefCounted<SchedulerWorker>( | 
 |       ThreadPriority::NORMAL, std::move(delegate), task_tracker.GetTrackedRef(), | 
 |       nullptr, SchedulerBackwardCompatibility::DISABLED); | 
 |   worker->Start(); | 
 |   worker->WakeUp(); | 
 |   delegate_raw->WaitUntilGetWorkReturned(); | 
 |  | 
 |   // The call to CoInitializeEx() should have returned S_OK to indicate that the | 
 |   // COM library wasn't already initialized on the thread. | 
 |   EXPECT_EQ(S_OK, delegate_raw->coinitialize_hresult()); | 
 |  | 
 |   worker->JoinForTesting(); | 
 | } | 
 |  | 
 | #endif  // defined(OS_WIN) | 
 |  | 
 | }  // namespace internal | 
 | }  // namespace base |