// 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/scheduler_worker.h" #include #include #include #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/synchronization/condition_variable.h" #include "base/synchronization/waitable_event.h" #include "base/task_scheduler/environment_config.h" #include "base/task_scheduler/scheduler_lock.h" #include "base/task_scheduler/scheduler_worker_observer.h" #include "base/task_scheduler/sequence.h" #include "base/task_scheduler/task.h" #include "base/task_scheduler/task_tracker.h" #include "base/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 #include "base/win/com_init_check_hook.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) override { ADD_FAILURE() << "Unexpected call to OnCanScheduleSequence()."; } SchedulerWorker::ThreadLabel GetThreadLabel() const override { return SchedulerWorker::ThreadLabel::DEDICATED; } void OnMainEntry(const SchedulerWorker* worker) override {} scoped_refptr GetWork(SchedulerWorker* worker) override { return nullptr; } void DidRunTask() override { ADD_FAILURE() << "Unexpected call to DidRunTask()"; } void ReEnqueueSequence(scoped_refptr 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 { protected: TaskSchedulerWorkerTest() : num_get_work_cv_(lock_.CreateConditionVariable()) {} void SetUp() override { worker_ = MakeRefCounted( ThreadPriority::NORMAL, std::make_unique(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> CreatedSequences() { AutoSchedulerLock auto_lock(lock_); return created_sequences_; } std::vector> EnqueuedSequences() { AutoSchedulerLock auto_lock(lock_); return re_enqueued_sequences_; } scoped_refptr 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 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(new Sequence); for (size_t i = 0; i < outer_->TasksPerSequence(); ++i) { Task task(FROM_HERE, BindOnce(&TaskSchedulerWorkerTest::RunTaskCallback, Unretained(outer_)), TaskTraits(), TimeDelta()); EXPECT_TRUE(outer_->task_tracker_.WillPostTask(&task)); 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) 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()); } 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 num_get_work_cv_; // Sequences created by GetWork(). std::vector> created_sequences_; // Sequences passed to EnqueueSequence(). std::vector> 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 { 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() = 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 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(new Sequence); 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_)), {WithBaseSyncPrimitives(), TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, TimeDelta()); EXPECT_TRUE(task_tracker_->WillPostTask(&task)); 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() { return controls_; } private: scoped_refptr work_sequence_; TaskTracker* const task_tracker_; scoped_refptr 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) { TaskTracker task_tracker("Test"); // Will be owned by SchedulerWorker. MockedControllableCleanupDelegate* delegate = new StrictMock(&task_tracker); scoped_refptr controls = delegate->controls(); controls->set_can_cleanup(true); EXPECT_CALL(*delegate, OnMainEntry(_)); auto worker = MakeRefCounted(ThreadPriority::NORMAL, WrapUnique(delegate), task_tracker.GetTrackedRef()); worker->Start(); worker->WakeUp(); controls->WaitForWorkToRun(); Mock::VerifyAndClear(delegate); controls->WaitForMainExit(); } TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringWork) { 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 delegate = std::make_unique(&task_tracker); scoped_refptr controls = delegate->controls(); controls->HaveWorkBlock(); auto worker = MakeRefCounted(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) { 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 delegate = std::make_unique(&task_tracker); scoped_refptr controls = delegate->controls(); auto worker = MakeRefCounted(ThreadPriority::NORMAL, std::move(delegate), task_tracker.GetTrackedRef()); worker->Start(); worker->WakeUp(); controls->WaitForCleanupRequest(); worker->Cleanup(); worker = nullptr; controls->WaitForDelegateDestroy(); } TEST(TaskSchedulerWorkerTest, WorkerCleanupDuringShutdown) { 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 delegate = std::make_unique(&task_tracker); scoped_refptr controls = delegate->controls(); controls->HaveWorkBlock(); auto worker = MakeRefCounted(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) { 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 delegate = std::make_unique(&task_tracker); scoped_refptr controls = delegate->controls(); controls->set_expect_get_work(false); auto worker = MakeRefCounted(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) { 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 delegate = std::make_unique(&task_tracker); scoped_refptr controls = delegate->controls(); controls->HaveWorkBlock(); auto worker = MakeRefCounted(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 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) { if (!CanUseBackgroundPriorityForSchedulerWorker()) return; TaskTracker task_tracker("Test"); std::unique_ptr delegate( new ExpectThreadPriorityDelegate); ExpectThreadPriorityDelegate* delegate_raw = delegate.get(); delegate_raw->SetExpectedThreadPriority(ThreadPriority::BACKGROUND); auto worker = MakeRefCounted(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 { EXPECT_CALL(*observer_, OnSchedulerWorkerMainExit()); } 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 observer; { TaskTracker task_tracker("Test"); auto delegate = std::make_unique(&observer); auto worker = MakeRefCounted(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 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) { TaskTracker task_tracker("Test"); auto delegate = std::make_unique(); CoInitializeDelegate* const delegate_raw = delegate.get(); // Create a worker with backward compatibility ENABLED. Wake it up and wait // until GetWork() returns. auto worker = MakeRefCounted( 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) { TaskTracker task_tracker("Test"); auto delegate = std::make_unique(); CoInitializeDelegate* const delegate_raw = delegate.get(); // Create a worker with backward compatibility DISABLED. Wake it up and wait // until GetWork() returns. auto worker = MakeRefCounted( 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