| // 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 |