| // 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/task_tracker.h" | |
| #include <memory> | |
| #include <utility> | |
| #include <vector> | |
| #include "base/bind.h" | |
| #include "base/bind_helpers.h" | |
| #include "base/callback.h" | |
| #include "base/logging.h" | |
| #include "base/macros.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/memory/ref_counted.h" | |
| #include "base/metrics/histogram_base.h" | |
| #include "base/metrics/histogram_samples.h" | |
| #include "base/metrics/statistics_recorder.h" | |
| #include "base/sequence_token.h" | |
| #include "base/sequenced_task_runner.h" | |
| #include "base/single_thread_task_runner.h" | |
| #include "base/synchronization/atomic_flag.h" | |
| #include "base/synchronization/waitable_event.h" | |
| #include "base/task/task_scheduler/scheduler_lock.h" | |
| #include "base/task/task_scheduler/task.h" | |
| #include "base/task/task_scheduler/test_utils.h" | |
| #include "base/task/task_traits.h" | |
| #include "base/test/gtest_util.h" | |
| #include "base/test/metrics/histogram_tester.h" | |
| #include "base/test/test_simple_task_runner.h" | |
| #include "base/test/test_timeouts.h" | |
| #include "base/threading/platform_thread.h" | |
| #include "base/threading/sequenced_task_runner_handle.h" | |
| #include "base/threading/simple_thread.h" | |
| #include "base/threading/thread_restrictions.h" | |
| #include "base/threading/thread_task_runner_handle.h" | |
| #include "starboard/types.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| namespace base { | |
| namespace internal { | |
| namespace { | |
| constexpr size_t kLoadTestNumIterations = 75; | |
| class MockCanScheduleSequenceObserver : public CanScheduleSequenceObserver { | |
| public: | |
| void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) override { | |
| MockOnCanScheduleSequence(sequence.get()); | |
| } | |
| MOCK_METHOD1(MockOnCanScheduleSequence, void(Sequence*)); | |
| }; | |
| // Invokes a closure asynchronously. | |
| class CallbackThread : public SimpleThread { | |
| public: | |
| explicit CallbackThread(const Closure& closure) | |
| : SimpleThread("CallbackThread"), closure_(closure) {} | |
| // Returns true once the callback returns. | |
| bool has_returned() { return has_returned_.IsSet(); } | |
| private: | |
| void Run() override { | |
| closure_.Run(); | |
| has_returned_.Set(); | |
| } | |
| const Closure closure_; | |
| AtomicFlag has_returned_; | |
| DISALLOW_COPY_AND_ASSIGN(CallbackThread); | |
| }; | |
| class ThreadPostingAndRunningTask : public SimpleThread { | |
| public: | |
| enum class Action { | |
| WILL_POST, | |
| RUN, | |
| WILL_POST_AND_RUN, | |
| }; | |
| ThreadPostingAndRunningTask(TaskTracker* tracker, | |
| Task* task, | |
| const TaskTraits& traits, | |
| Action action, | |
| bool expect_post_succeeds) | |
| : SimpleThread("ThreadPostingAndRunningTask"), | |
| tracker_(tracker), | |
| owned_task_(FROM_HERE, OnceClosure(), TimeDelta()), | |
| task_(task), | |
| traits_(traits), | |
| action_(action), | |
| expect_post_succeeds_(expect_post_succeeds) { | |
| EXPECT_TRUE(task_); | |
| // Ownership of the Task is required to run it. | |
| EXPECT_NE(Action::RUN, action_); | |
| EXPECT_NE(Action::WILL_POST_AND_RUN, action_); | |
| } | |
| ThreadPostingAndRunningTask(TaskTracker* tracker, | |
| Task task, | |
| const TaskTraits& traits, | |
| Action action, | |
| bool expect_post_succeeds) | |
| : SimpleThread("ThreadPostingAndRunningTask"), | |
| tracker_(tracker), | |
| owned_task_(std::move(task)), | |
| task_(&owned_task_), | |
| traits_(traits), | |
| action_(action), | |
| expect_post_succeeds_(expect_post_succeeds) { | |
| EXPECT_TRUE(owned_task_.task); | |
| } | |
| private: | |
| void Run() override { | |
| bool post_succeeded = true; | |
| if (action_ == Action::WILL_POST || action_ == Action::WILL_POST_AND_RUN) { | |
| post_succeeded = | |
| tracker_->WillPostTask(task_, traits_.shutdown_behavior()); | |
| EXPECT_EQ(expect_post_succeeds_, post_succeeded); | |
| } | |
| if (post_succeeded && | |
| (action_ == Action::RUN || action_ == Action::WILL_POST_AND_RUN)) { | |
| EXPECT_TRUE(owned_task_.task); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> | |
| never_notified_observer; | |
| auto sequence = tracker_->WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(owned_task_), traits_), | |
| &never_notified_observer); | |
| ASSERT_TRUE(sequence); | |
| // Expect RunAndPopNextTask to return nullptr since |sequence| is empty | |
| // after popping a task from it. | |
| EXPECT_FALSE(tracker_->RunAndPopNextTask(std::move(sequence), | |
| &never_notified_observer)); | |
| } | |
| } | |
| TaskTracker* const tracker_; | |
| Task owned_task_; | |
| Task* task_; | |
| const TaskTraits traits_; | |
| const Action action_; | |
| const bool expect_post_succeeds_; | |
| DISALLOW_COPY_AND_ASSIGN(ThreadPostingAndRunningTask); | |
| }; | |
| class ScopedSetSingletonAllowed { | |
| public: | |
| ScopedSetSingletonAllowed(bool singleton_allowed) | |
| : previous_value_( | |
| ThreadRestrictions::SetSingletonAllowed(singleton_allowed)) {} | |
| ~ScopedSetSingletonAllowed() { | |
| ThreadRestrictions::SetSingletonAllowed(previous_value_); | |
| } | |
| private: | |
| const bool previous_value_; | |
| }; | |
| class TaskSchedulerTaskTrackerTest | |
| : public testing::TestWithParam<TaskShutdownBehavior> { | |
| protected: | |
| TaskSchedulerTaskTrackerTest() | |
| : recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()) { | |
| } | |
| // Creates a task. | |
| Task CreateTask() { | |
| return Task( | |
| FROM_HERE, | |
| Bind(&TaskSchedulerTaskTrackerTest::RunTaskCallback, Unretained(this)), | |
| TimeDelta()); | |
| } | |
| void DispatchAndRunTaskWithTracker(Task task, const TaskTraits& traits) { | |
| auto sequence = tracker_.WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(task), traits), | |
| &never_notified_observer_); | |
| ASSERT_TRUE(sequence); | |
| tracker_.RunAndPopNextTask(std::move(sequence), &never_notified_observer_); | |
| } | |
| // Calls tracker_->Shutdown() on a new thread. When this returns, Shutdown() | |
| // method has been entered on the new thread, but it hasn't necessarily | |
| // returned. | |
| void CallShutdownAsync() { | |
| ASSERT_FALSE(thread_calling_shutdown_); | |
| thread_calling_shutdown_.reset(new CallbackThread( | |
| Bind(&TaskTracker::Shutdown, Unretained(&tracker_)))); | |
| thread_calling_shutdown_->Start(); | |
| while (!tracker_.HasShutdownStarted()) | |
| PlatformThread::YieldCurrentThread(); | |
| } | |
| void WaitForAsyncIsShutdownComplete() { | |
| ASSERT_TRUE(thread_calling_shutdown_); | |
| thread_calling_shutdown_->Join(); | |
| EXPECT_TRUE(thread_calling_shutdown_->has_returned()); | |
| EXPECT_TRUE(tracker_.IsShutdownComplete()); | |
| } | |
| void VerifyAsyncShutdownInProgress() { | |
| ASSERT_TRUE(thread_calling_shutdown_); | |
| EXPECT_FALSE(thread_calling_shutdown_->has_returned()); | |
| EXPECT_TRUE(tracker_.HasShutdownStarted()); | |
| EXPECT_FALSE(tracker_.IsShutdownComplete()); | |
| } | |
| // Calls tracker_->FlushForTesting() on a new thread. | |
| void CallFlushFromAnotherThread() { | |
| ASSERT_FALSE(thread_calling_flush_); | |
| thread_calling_flush_.reset(new CallbackThread( | |
| Bind(&TaskTracker::FlushForTesting, Unretained(&tracker_)))); | |
| thread_calling_flush_->Start(); | |
| } | |
| void WaitForAsyncFlushReturned() { | |
| ASSERT_TRUE(thread_calling_flush_); | |
| thread_calling_flush_->Join(); | |
| EXPECT_TRUE(thread_calling_flush_->has_returned()); | |
| } | |
| void VerifyAsyncFlushInProgress() { | |
| ASSERT_TRUE(thread_calling_flush_); | |
| EXPECT_FALSE(thread_calling_flush_->has_returned()); | |
| } | |
| size_t NumTasksExecuted() { | |
| AutoSchedulerLock auto_lock(lock_); | |
| return num_tasks_executed_; | |
| } | |
| std::unique_ptr<StatisticsRecorder> recorder_for_testing_; | |
| TaskTracker tracker_ = {"Test"}; | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer_; | |
| private: | |
| void RunTaskCallback() { | |
| AutoSchedulerLock auto_lock(lock_); | |
| ++num_tasks_executed_; | |
| } | |
| std::unique_ptr<CallbackThread> thread_calling_shutdown_; | |
| std::unique_ptr<CallbackThread> thread_calling_flush_; | |
| // Synchronizes accesses to |num_tasks_executed_|. | |
| SchedulerLock lock_; | |
| size_t num_tasks_executed_ = 0; | |
| DISALLOW_COPY_AND_ASSIGN(TaskSchedulerTaskTrackerTest); | |
| }; | |
| #define WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED() \ | |
| do { \ | |
| SCOPED_TRACE(""); \ | |
| WaitForAsyncIsShutdownComplete(); \ | |
| } while (false) | |
| #define VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS() \ | |
| do { \ | |
| SCOPED_TRACE(""); \ | |
| VerifyAsyncShutdownInProgress(); \ | |
| } while (false) | |
| #define WAIT_FOR_ASYNC_FLUSH_RETURNED() \ | |
| do { \ | |
| SCOPED_TRACE(""); \ | |
| WaitForAsyncFlushReturned(); \ | |
| } while (false) | |
| #define VERIFY_ASYNC_FLUSH_IN_PROGRESS() \ | |
| do { \ | |
| SCOPED_TRACE(""); \ | |
| VerifyAsyncFlushInProgress(); \ | |
| } while (false) | |
| } // namespace | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunBeforeShutdown) { | |
| Task task(CreateTask()); | |
| // Inform |task_tracker_| that |task| will be posted. | |
| EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); | |
| // Run the task. | |
| EXPECT_EQ(0U, NumTasksExecuted()); | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| EXPECT_EQ(1U, NumTasksExecuted()); | |
| // Shutdown() shouldn't block. | |
| tracker_.Shutdown(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunLongTaskBeforeShutdown) { | |
| // Create a task that signals |task_running| and blocks until |task_barrier| | |
| // is signaled. | |
| WaitableEvent task_running(WaitableEvent::ResetPolicy::AUTOMATIC, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| WaitableEvent task_barrier(WaitableEvent::ResetPolicy::AUTOMATIC, | |
| WaitableEvent::InitialState::NOT_SIGNALED); | |
| Task blocked_task( | |
| FROM_HERE, | |
| Bind( | |
| [](WaitableEvent* task_running, WaitableEvent* task_barrier) { | |
| task_running->Signal(); | |
| task_barrier->Wait(); | |
| }, | |
| Unretained(&task_running), Unretained(&task_barrier)), | |
| TimeDelta()); | |
| // Inform |task_tracker_| that |blocked_task| will be posted. | |
| EXPECT_TRUE(tracker_.WillPostTask(&blocked_task, GetParam())); | |
| // Create a thread to run the task. Wait until the task starts running. | |
| ThreadPostingAndRunningTask thread_running_task( | |
| &tracker_, std::move(blocked_task), | |
| TaskTraits(WithBaseSyncPrimitives(), GetParam()), | |
| ThreadPostingAndRunningTask::Action::RUN, false); | |
| thread_running_task.Start(); | |
| task_running.Wait(); | |
| // Initiate shutdown after the task has started to run. | |
| CallShutdownAsync(); | |
| if (GetParam() == TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) { | |
| // Shutdown should complete even with a CONTINUE_ON_SHUTDOWN in progress. | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| } else { | |
| // Shutdown should block with any non CONTINUE_ON_SHUTDOWN task in progress. | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| } | |
| // Unblock the task. | |
| task_barrier.Signal(); | |
| thread_running_task.Join(); | |
| // Shutdown should now complete for a non CONTINUE_ON_SHUTDOWN task. | |
| if (GetParam() != TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostBeforeShutdownRunDuringShutdown) { | |
| // Inform |task_tracker_| that a task will be posted. | |
| Task task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); | |
| // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to | |
| // block shutdown. | |
| Task block_shutdown_task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task, | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| // Call Shutdown() asynchronously. | |
| CallShutdownAsync(); | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| // Try to run |task|. It should only run it it's BLOCK_SHUTDOWN. Otherwise it | |
| // should be discarded. | |
| EXPECT_EQ(0U, NumTasksExecuted()); | |
| const bool should_run = GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN; | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| EXPECT_EQ(should_run ? 1U : 0U, NumTasksExecuted()); | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| // Unblock shutdown by running the remaining BLOCK_SHUTDOWN task. | |
| DispatchAndRunTaskWithTracker(std::move(block_shutdown_task), | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN); | |
| EXPECT_EQ(should_run ? 2U : 1U, NumTasksExecuted()); | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostBeforeShutdownRunAfterShutdown) { | |
| // Inform |task_tracker_| that a task will be posted. | |
| Task task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); | |
| // Call Shutdown() asynchronously. | |
| CallShutdownAsync(); | |
| EXPECT_EQ(0U, NumTasksExecuted()); | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) { | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| // Run the task to unblock shutdown. | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| EXPECT_EQ(1U, NumTasksExecuted()); | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| // It is not possible to test running a BLOCK_SHUTDOWN task posted before | |
| // shutdown after shutdown because Shutdown() won't return if there are | |
| // pending BLOCK_SHUTDOWN tasks. | |
| } else { | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| // The task shouldn't be allowed to run after shutdown. | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| EXPECT_EQ(0U, NumTasksExecuted()); | |
| } | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostAndRunDuringShutdown) { | |
| // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to | |
| // block shutdown. | |
| Task block_shutdown_task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task, | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| // Call Shutdown() asynchronously. | |
| CallShutdownAsync(); | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) { | |
| // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted. | |
| Task task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); | |
| // Run the BLOCK_SHUTDOWN task. | |
| EXPECT_EQ(0U, NumTasksExecuted()); | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| EXPECT_EQ(1U, NumTasksExecuted()); | |
| } else { | |
| // It shouldn't be allowed to post a non BLOCK_SHUTDOWN task. | |
| Task task(CreateTask()); | |
| EXPECT_FALSE(tracker_.WillPostTask(&task, GetParam())); | |
| // Don't try to run the task, because it wasn't allowed to be posted. | |
| } | |
| // Unblock shutdown by running |block_shutdown_task|. | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| DispatchAndRunTaskWithTracker(std::move(block_shutdown_task), | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN); | |
| EXPECT_EQ(GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN ? 2U : 1U, | |
| NumTasksExecuted()); | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, WillPostAfterShutdown) { | |
| tracker_.Shutdown(); | |
| Task task(CreateTask()); | |
| // |task_tracker_| shouldn't allow a task to be posted after shutdown. | |
| EXPECT_FALSE(tracker_.WillPostTask(&task, GetParam())); | |
| } | |
| // Verify that BLOCK_SHUTDOWN and SKIP_ON_SHUTDOWN tasks can | |
| // AssertSingletonAllowed() but CONTINUE_ON_SHUTDOWN tasks can't. | |
| TEST_P(TaskSchedulerTaskTrackerTest, SingletonAllowed) { | |
| const bool can_use_singletons = | |
| (GetParam() != TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN); | |
| Task task(FROM_HERE, BindOnce(&ThreadRestrictions::AssertSingletonAllowed), | |
| TimeDelta()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); | |
| // Set the singleton allowed bit to the opposite of what it is expected to be | |
| // when |tracker| runs |task| to verify that |tracker| actually sets the | |
| // correct value. | |
| ScopedSetSingletonAllowed scoped_singleton_allowed(!can_use_singletons); | |
| // Running the task should fail iff the task isn't allowed to use singletons. | |
| if (can_use_singletons) { | |
| DispatchAndRunTaskWithTracker(std::move(task), GetParam()); | |
| } else { | |
| EXPECT_DCHECK_DEATH( | |
| { DispatchAndRunTaskWithTracker(std::move(task), GetParam()); }); | |
| } | |
| } | |
| // Verify that AssertIOAllowed() succeeds only for a MayBlock() task. | |
| TEST_P(TaskSchedulerTaskTrackerTest, IOAllowed) { | |
| // Unset the IO allowed bit. Expect TaskTracker to set it before running a | |
| // task with the MayBlock() trait. | |
| ThreadRestrictions::SetIOAllowed(false); | |
| Task task_with_may_block(FROM_HERE, Bind([]() { | |
| // Shouldn't fail. | |
| AssertBlockingAllowed(); | |
| }), | |
| TimeDelta()); | |
| TaskTraits traits_with_may_block = TaskTraits(MayBlock(), GetParam()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&task_with_may_block, | |
| traits_with_may_block.shutdown_behavior())); | |
| DispatchAndRunTaskWithTracker(std::move(task_with_may_block), | |
| traits_with_may_block); | |
| // Set the IO allowed bit. Expect TaskTracker to unset it before running a | |
| // task without the MayBlock() trait. | |
| ThreadRestrictions::SetIOAllowed(true); | |
| Task task_without_may_block( | |
| FROM_HERE, | |
| Bind([]() { EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); }); }), | |
| TimeDelta()); | |
| TaskTraits traits_without_may_block = TaskTraits(GetParam()); | |
| EXPECT_TRUE(tracker_.WillPostTask( | |
| &task_without_may_block, traits_without_may_block.shutdown_behavior())); | |
| DispatchAndRunTaskWithTracker(std::move(task_without_may_block), | |
| traits_without_may_block); | |
| } | |
| static void RunTaskRunnerHandleVerificationTask(TaskTracker* tracker, | |
| Task verify_task, | |
| TaskTraits traits) { | |
| // Pretend |verify_task| is posted to respect TaskTracker's contract. | |
| EXPECT_TRUE(tracker->WillPostTask(&verify_task, traits.shutdown_behavior())); | |
| // Confirm that the test conditions are right (no TaskRunnerHandles set | |
| // already). | |
| EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet()); | |
| EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet()); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; | |
| auto sequence = tracker->WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(verify_task), traits), | |
| &never_notified_observer); | |
| ASSERT_TRUE(sequence); | |
| tracker->RunAndPopNextTask(std::move(sequence), &never_notified_observer); | |
| // TaskRunnerHandle state is reset outside of task's scope. | |
| EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet()); | |
| EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet()); | |
| } | |
| static void VerifyNoTaskRunnerHandle() { | |
| EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet()); | |
| EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet()); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, TaskRunnerHandleIsNotSetOnParallel) { | |
| // Create a task that will verify that TaskRunnerHandles are not set in its | |
| // scope per no TaskRunner ref being set to it. | |
| Task verify_task(FROM_HERE, BindOnce(&VerifyNoTaskRunnerHandle), TimeDelta()); | |
| RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task), | |
| TaskTraits(GetParam())); | |
| } | |
| static void VerifySequencedTaskRunnerHandle( | |
| const SequencedTaskRunner* expected_task_runner) { | |
| EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet()); | |
| EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet()); | |
| EXPECT_EQ(expected_task_runner, SequencedTaskRunnerHandle::Get()); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, | |
| SequencedTaskRunnerHandleIsSetOnSequenced) { | |
| scoped_refptr<SequencedTaskRunner> test_task_runner(new TestSimpleTaskRunner); | |
| // Create a task that will verify that SequencedTaskRunnerHandle is properly | |
| // set to |test_task_runner| in its scope per |sequenced_task_runner_ref| | |
| // being set to it. | |
| Task verify_task(FROM_HERE, | |
| BindOnce(&VerifySequencedTaskRunnerHandle, | |
| Unretained(test_task_runner.get())), | |
| TimeDelta()); | |
| verify_task.sequenced_task_runner_ref = test_task_runner; | |
| RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task), | |
| TaskTraits(GetParam())); | |
| } | |
| static void VerifyThreadTaskRunnerHandle( | |
| const SingleThreadTaskRunner* expected_task_runner) { | |
| EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet()); | |
| // SequencedTaskRunnerHandle inherits ThreadTaskRunnerHandle for thread. | |
| EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet()); | |
| EXPECT_EQ(expected_task_runner, ThreadTaskRunnerHandle::Get()); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, | |
| ThreadTaskRunnerHandleIsSetOnSingleThreaded) { | |
| scoped_refptr<SingleThreadTaskRunner> test_task_runner( | |
| new TestSimpleTaskRunner); | |
| // Create a task that will verify that ThreadTaskRunnerHandle is properly set | |
| // to |test_task_runner| in its scope per |single_thread_task_runner_ref| | |
| // being set on it. | |
| Task verify_task(FROM_HERE, | |
| BindOnce(&VerifyThreadTaskRunnerHandle, | |
| Unretained(test_task_runner.get())), | |
| TimeDelta()); | |
| verify_task.single_thread_task_runner_ref = test_task_runner; | |
| RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task), | |
| TaskTraits(GetParam())); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushPendingDelayedTask) { | |
| Task delayed_task(FROM_HERE, DoNothing(), TimeDelta::FromDays(1)); | |
| tracker_.WillPostTask(&delayed_task, GetParam()); | |
| // FlushForTesting() should return even if the delayed task didn't run. | |
| tracker_.FlushForTesting(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingDelayedTask) { | |
| Task delayed_task(FROM_HERE, DoNothing(), TimeDelta::FromDays(1)); | |
| tracker_.WillPostTask(&delayed_task, GetParam()); | |
| // FlushAsyncForTesting() should callback even if the delayed task didn't run. | |
| bool called_back = false; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce([](bool* called_back) { *called_back = true; }, | |
| Unretained(&called_back))); | |
| EXPECT_TRUE(called_back); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushPendingUndelayedTask) { | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushForTesting() shouldn't return before the undelayed task runs. | |
| CallFlushFromAnotherThread(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // FlushForTesting() should return after the undelayed task runs. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| WAIT_FOR_ASYNC_FLUSH_RETURNED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingUndelayedTask) { | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. | |
| WaitableEvent event; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce(&WaitableEvent::Signal, Unretained(&event))); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // FlushAsyncForTesting() should callback after the undelayed task runs. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| event.Wait(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlush) { | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushForTesting() shouldn't return before the undelayed task runs. | |
| CallFlushFromAnotherThread(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // Simulate posting another undelayed task. | |
| Task other_undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&other_undelayed_task, GetParam()); | |
| // Run the first undelayed task. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| // FlushForTesting() shouldn't return before the second undelayed task runs. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // FlushForTesting() should return after the second undelayed task runs. | |
| DispatchAndRunTaskWithTracker(std::move(other_undelayed_task), GetParam()); | |
| WAIT_FOR_ASYNC_FLUSH_RETURNED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlushAsyncForTesting) { | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. | |
| WaitableEvent event; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce(&WaitableEvent::Signal, Unretained(&event))); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // Simulate posting another undelayed task. | |
| Task other_undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&other_undelayed_task, GetParam()); | |
| // Run the first undelayed task. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the second undelayed task | |
| // runs. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // FlushAsyncForTesting() should callback after the second undelayed task | |
| // runs. | |
| DispatchAndRunTaskWithTracker(std::move(other_undelayed_task), GetParam()); | |
| event.Wait(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, RunDelayedTaskDuringFlush) { | |
| // Simulate posting a delayed and an undelayed task. | |
| Task delayed_task(FROM_HERE, DoNothing(), TimeDelta::FromDays(1)); | |
| tracker_.WillPostTask(&delayed_task, GetParam()); | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushForTesting() shouldn't return before the undelayed task runs. | |
| CallFlushFromAnotherThread(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // Run the delayed task. | |
| DispatchAndRunTaskWithTracker(std::move(delayed_task), GetParam()); | |
| // FlushForTesting() shouldn't return since there is still a pending undelayed | |
| // task. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // Run the undelayed task. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| // FlushForTesting() should now return. | |
| WAIT_FOR_ASYNC_FLUSH_RETURNED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, RunDelayedTaskDuringFlushAsyncForTesting) { | |
| // Simulate posting a delayed and an undelayed task. | |
| Task delayed_task(FROM_HERE, DoNothing(), TimeDelta::FromDays(1)); | |
| tracker_.WillPostTask(&delayed_task, GetParam()); | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. | |
| WaitableEvent event; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce(&WaitableEvent::Signal, Unretained(&event))); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // Run the delayed task. | |
| DispatchAndRunTaskWithTracker(std::move(delayed_task), GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback since there is still a pending | |
| // undelayed task. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // Run the undelayed task. | |
| DispatchAndRunTaskWithTracker(std::move(undelayed_task), GetParam()); | |
| // FlushAsyncForTesting() should now callback. | |
| event.Wait(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushAfterShutdown) { | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) | |
| return; | |
| // Simulate posting a task. | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // Shutdown() should return immediately since there are no pending | |
| // BLOCK_SHUTDOWN tasks. | |
| tracker_.Shutdown(); | |
| // FlushForTesting() should return immediately after shutdown, even if an | |
| // undelayed task hasn't run. | |
| tracker_.FlushForTesting(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, FlushAfterShutdownAsync) { | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) | |
| return; | |
| // Simulate posting a task. | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // Shutdown() should return immediately since there are no pending | |
| // BLOCK_SHUTDOWN tasks. | |
| tracker_.Shutdown(); | |
| // FlushForTesting() should callback immediately after shutdown, even if an | |
| // undelayed task hasn't run. | |
| bool called_back = false; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce([](bool* called_back) { *called_back = true; }, | |
| Unretained(&called_back))); | |
| EXPECT_TRUE(called_back); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, ShutdownDuringFlush) { | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) | |
| return; | |
| // Simulate posting a task. | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushForTesting() shouldn't return before the undelayed task runs or | |
| // shutdown completes. | |
| CallFlushFromAnotherThread(); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| VERIFY_ASYNC_FLUSH_IN_PROGRESS(); | |
| // Shutdown() should return immediately since there are no pending | |
| // BLOCK_SHUTDOWN tasks. | |
| tracker_.Shutdown(); | |
| // FlushForTesting() should now return, even if an undelayed task hasn't run. | |
| WAIT_FOR_ASYNC_FLUSH_RETURNED(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, ShutdownDuringFlushAsyncForTesting) { | |
| if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) | |
| return; | |
| // Simulate posting a task. | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the undelayed task runs or | |
| // shutdown completes. | |
| WaitableEvent event; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce(&WaitableEvent::Signal, Unretained(&event))); | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| EXPECT_FALSE(event.IsSignaled()); | |
| // Shutdown() should return immediately since there are no pending | |
| // BLOCK_SHUTDOWN tasks. | |
| tracker_.Shutdown(); | |
| // FlushAsyncForTesting() should now callback, even if an undelayed task | |
| // hasn't run. | |
| event.Wait(); | |
| } | |
| TEST_P(TaskSchedulerTaskTrackerTest, DoublePendingFlushAsyncForTestingFails) { | |
| Task undelayed_task(FROM_HERE, DoNothing(), TimeDelta()); | |
| tracker_.WillPostTask(&undelayed_task, GetParam()); | |
| // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. | |
| bool called_back = false; | |
| tracker_.FlushAsyncForTesting( | |
| BindOnce([](bool* called_back) { *called_back = true; }, | |
| Unretained(&called_back))); | |
| EXPECT_FALSE(called_back); | |
| EXPECT_DCHECK_DEATH({ tracker_.FlushAsyncForTesting(BindOnce([]() {})); }); | |
| } | |
| // Verify that a delayed task does not block shutdown, regardless of its | |
| // shutdown behavior. | |
| TEST_P(TaskSchedulerTaskTrackerTest, DelayedTasksDoNotBlockShutdown) { | |
| // Simulate posting a delayed task. | |
| Task delayed_task(FROM_HERE, DoNothing(), TimeDelta::FromDays(1)); | |
| EXPECT_TRUE(tracker_.WillPostTask(&delayed_task, GetParam())); | |
| // Since the delayed task doesn't block shutdown, a call to Shutdown() should | |
| // not hang. | |
| tracker_.Shutdown(); | |
| } | |
| INSTANTIATE_TEST_CASE_P( | |
| ContinueOnShutdown, | |
| TaskSchedulerTaskTrackerTest, | |
| ::testing::Values(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)); | |
| INSTANTIATE_TEST_CASE_P( | |
| SkipOnShutdown, | |
| TaskSchedulerTaskTrackerTest, | |
| ::testing::Values(TaskShutdownBehavior::SKIP_ON_SHUTDOWN)); | |
| INSTANTIATE_TEST_CASE_P( | |
| BlockShutdown, | |
| TaskSchedulerTaskTrackerTest, | |
| ::testing::Values(TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| namespace { | |
| void ExpectSequenceToken(SequenceToken sequence_token) { | |
| EXPECT_EQ(sequence_token, SequenceToken::GetForCurrentThread()); | |
| } | |
| } // namespace | |
| // Verify that SequenceToken::GetForCurrentThread() returns the Sequence's token | |
| // when a Task runs. | |
| TEST_F(TaskSchedulerTaskTrackerTest, CurrentSequenceToken) { | |
| scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(TaskTraits()); | |
| const SequenceToken sequence_token = sequence->token(); | |
| Task task(FROM_HERE, Bind(&ExpectSequenceToken, sequence_token), TimeDelta()); | |
| tracker_.WillPostTask(&task, sequence->traits().shutdown_behavior()); | |
| sequence->PushTask(std::move(task)); | |
| EXPECT_FALSE(SequenceToken::GetForCurrentThread().IsValid()); | |
| sequence = tracker_.WillScheduleSequence(std::move(sequence), | |
| &never_notified_observer_); | |
| ASSERT_TRUE(sequence); | |
| tracker_.RunAndPopNextTask(std::move(sequence), &never_notified_observer_); | |
| EXPECT_FALSE(SequenceToken::GetForCurrentThread().IsValid()); | |
| } | |
| TEST_F(TaskSchedulerTaskTrackerTest, LoadWillPostAndRunBeforeShutdown) { | |
| // Post and run tasks asynchronously. | |
| std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> threads; | |
| for (size_t i = 0; i < kLoadTestNumIterations; ++i) { | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); | |
| threads.back()->Start(); | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); | |
| threads.back()->Start(); | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::BLOCK_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); | |
| threads.back()->Start(); | |
| } | |
| for (const auto& thread : threads) | |
| thread->Join(); | |
| // Expect all tasks to be executed. | |
| EXPECT_EQ(kLoadTestNumIterations * 3, NumTasksExecuted()); | |
| // Should return immediately because no tasks are blocking shutdown. | |
| tracker_.Shutdown(); | |
| } | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| LoadWillPostBeforeShutdownAndRunDuringShutdown) { | |
| // Post tasks asynchronously. | |
| std::vector<Task> tasks_continue_on_shutdown; | |
| std::vector<Task> tasks_skip_on_shutdown; | |
| std::vector<Task> tasks_block_shutdown; | |
| for (size_t i = 0; i < kLoadTestNumIterations; ++i) { | |
| tasks_continue_on_shutdown.push_back(CreateTask()); | |
| tasks_skip_on_shutdown.push_back(CreateTask()); | |
| tasks_block_shutdown.push_back(CreateTask()); | |
| } | |
| TaskTraits traits_continue_on_shutdown = | |
| TaskTraits(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN); | |
| TaskTraits traits_skip_on_shutdown = | |
| TaskTraits(TaskShutdownBehavior::SKIP_ON_SHUTDOWN); | |
| TaskTraits traits_block_shutdown = | |
| TaskTraits(TaskShutdownBehavior::BLOCK_SHUTDOWN); | |
| std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> post_threads; | |
| for (size_t i = 0; i < kLoadTestNumIterations; ++i) { | |
| post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, &tasks_continue_on_shutdown[i], traits_continue_on_shutdown, | |
| ThreadPostingAndRunningTask::Action::WILL_POST, true)); | |
| post_threads.back()->Start(); | |
| post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, &tasks_skip_on_shutdown[i], traits_skip_on_shutdown, | |
| ThreadPostingAndRunningTask::Action::WILL_POST, true)); | |
| post_threads.back()->Start(); | |
| post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, &tasks_block_shutdown[i], traits_block_shutdown, | |
| ThreadPostingAndRunningTask::Action::WILL_POST, true)); | |
| post_threads.back()->Start(); | |
| } | |
| for (const auto& thread : post_threads) | |
| thread->Join(); | |
| // Call Shutdown() asynchronously. | |
| CallShutdownAsync(); | |
| // Run tasks asynchronously. | |
| std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> run_threads; | |
| for (size_t i = 0; i < kLoadTestNumIterations; ++i) { | |
| run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, std::move(tasks_continue_on_shutdown[i]), | |
| traits_continue_on_shutdown, ThreadPostingAndRunningTask::Action::RUN, | |
| false)); | |
| run_threads.back()->Start(); | |
| run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, std::move(tasks_skip_on_shutdown[i]), | |
| traits_skip_on_shutdown, ThreadPostingAndRunningTask::Action::RUN, | |
| false)); | |
| run_threads.back()->Start(); | |
| run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, std::move(tasks_block_shutdown[i]), traits_block_shutdown, | |
| ThreadPostingAndRunningTask::Action::RUN, false)); | |
| run_threads.back()->Start(); | |
| } | |
| for (const auto& thread : run_threads) | |
| thread->Join(); | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| // Expect BLOCK_SHUTDOWN tasks to have been executed. | |
| EXPECT_EQ(kLoadTestNumIterations, NumTasksExecuted()); | |
| } | |
| TEST_F(TaskSchedulerTaskTrackerTest, LoadWillPostAndRunDuringShutdown) { | |
| // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to | |
| // block shutdown. | |
| Task block_shutdown_task(CreateTask()); | |
| EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task, | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| // Call Shutdown() asynchronously. | |
| CallShutdownAsync(); | |
| // Post and run tasks asynchronously. | |
| std::vector<std::unique_ptr<ThreadPostingAndRunningTask>> threads; | |
| for (size_t i = 0; i < kLoadTestNumIterations; ++i) { | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); | |
| threads.back()->Start(); | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); | |
| threads.back()->Start(); | |
| threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( | |
| &tracker_, CreateTask(), | |
| TaskTraits(TaskShutdownBehavior::BLOCK_SHUTDOWN), | |
| ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); | |
| threads.back()->Start(); | |
| } | |
| for (const auto& thread : threads) | |
| thread->Join(); | |
| // Expect BLOCK_SHUTDOWN tasks to have been executed. | |
| EXPECT_EQ(kLoadTestNumIterations, NumTasksExecuted()); | |
| // Shutdown() shouldn't return before |block_shutdown_task| is executed. | |
| VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); | |
| // Unblock shutdown by running |block_shutdown_task|. | |
| DispatchAndRunTaskWithTracker( | |
| std::move(block_shutdown_task), | |
| TaskTraits(TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| EXPECT_EQ(kLoadTestNumIterations + 1, NumTasksExecuted()); | |
| WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); | |
| } | |
| // Verify that RunAndPopNextTask() returns the sequence from which it ran a task | |
| // when it can be rescheduled. | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| RunAndPopNextTaskReturnsSequenceToReschedule) { | |
| TaskTraits default_traits = {}; | |
| Task task_1(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_1, default_traits.shutdown_behavior())); | |
| Task task_2(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_2, default_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence = | |
| test::CreateSequenceWithTask(std::move(task_1), default_traits); | |
| sequence->PushTask(std::move(task_2)); | |
| EXPECT_EQ(sequence, tracker_.WillScheduleSequence(sequence, nullptr)); | |
| EXPECT_EQ(sequence, tracker_.RunAndPopNextTask(sequence, nullptr)); | |
| } | |
| namespace { | |
| void TestWillScheduleBestEffortSequenceWithMaxBestEffortSequences( | |
| int max_num_scheduled_best_effort_sequences, | |
| TaskTracker& tracker) { | |
| // Simulate posting |max_num_scheduled_best_effort_sequences| best-effort | |
| // tasks and scheduling the associated sequences. This should succeed. | |
| std::vector<scoped_refptr<Sequence>> scheduled_sequences; | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; | |
| TaskTraits best_effort_traits = TaskTraits(TaskPriority::BEST_EFFORT); | |
| for (int i = 0; i < max_num_scheduled_best_effort_sequences; ++i) { | |
| Task task(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker.WillPostTask(&task, best_effort_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence = | |
| test::CreateSequenceWithTask(std::move(task), best_effort_traits); | |
| EXPECT_EQ(sequence, | |
| tracker.WillScheduleSequence(sequence, &never_notified_observer)); | |
| scheduled_sequences.push_back(std::move(sequence)); | |
| } | |
| // Simulate posting extra best-effort tasks and scheduling the associated | |
| // sequences. This should fail because the maximum number of best-effort | |
| // sequences that can be scheduled concurrently is already reached. | |
| std::vector<std::unique_ptr<bool>> extra_tasks_did_run; | |
| std::vector< | |
| std::unique_ptr<testing::StrictMock<MockCanScheduleSequenceObserver>>> | |
| extra_observers; | |
| std::vector<scoped_refptr<Sequence>> extra_sequences; | |
| for (int i = 0; i < max_num_scheduled_best_effort_sequences; ++i) { | |
| extra_tasks_did_run.push_back(std::make_unique<bool>()); | |
| Task extra_task( | |
| FROM_HERE, | |
| BindOnce([](bool* extra_task_did_run) { *extra_task_did_run = true; }, | |
| Unretained(extra_tasks_did_run.back().get())), | |
| TimeDelta()); | |
| EXPECT_TRUE(tracker.WillPostTask(&extra_task, | |
| best_effort_traits.shutdown_behavior())); | |
| extra_sequences.push_back(test::CreateSequenceWithTask( | |
| std::move(extra_task), best_effort_traits)); | |
| extra_observers.push_back( | |
| std::make_unique< | |
| testing::StrictMock<MockCanScheduleSequenceObserver>>()); | |
| EXPECT_EQ(nullptr, | |
| tracker.WillScheduleSequence(extra_sequences.back(), | |
| extra_observers.back().get())); | |
| } | |
| // Run the sequences scheduled at the beginning of the test. Expect an | |
| // observer from |extra_observer| to be notified every time a task finishes to | |
| // run. | |
| for (int i = 0; i < max_num_scheduled_best_effort_sequences; ++i) { | |
| EXPECT_CALL(*extra_observers[i].get(), | |
| MockOnCanScheduleSequence(extra_sequences[i].get())); | |
| EXPECT_FALSE(tracker.RunAndPopNextTask(scheduled_sequences[i], | |
| &never_notified_observer)); | |
| testing::Mock::VerifyAndClear(extra_observers[i].get()); | |
| } | |
| // Run the extra sequences. | |
| for (int i = 0; i < max_num_scheduled_best_effort_sequences; ++i) { | |
| EXPECT_FALSE(*extra_tasks_did_run[i]); | |
| EXPECT_FALSE(tracker.RunAndPopNextTask(extra_sequences[i], | |
| &never_notified_observer)); | |
| EXPECT_TRUE(*extra_tasks_did_run[i]); | |
| } | |
| } | |
| } // namespace | |
| // Verify that WillScheduleSequence() returns nullptr when it receives a | |
| // best-effort sequence and the maximum number of best-effort sequences that can | |
| // be scheduled concurrently is reached. Verify that an observer is notified | |
| // when a best-effort sequence can be scheduled (i.e. when one of the previously | |
| // scheduled best-effort sequences has run). | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| WillScheduleBestEffortSequenceWithMaxBestEffortSequences) { | |
| constexpr int kMaxNumScheduledBestEffortSequences = 2; | |
| TaskTracker tracker("Test", kMaxNumScheduledBestEffortSequences); | |
| TestWillScheduleBestEffortSequenceWithMaxBestEffortSequences( | |
| kMaxNumScheduledBestEffortSequences, tracker); | |
| } | |
| // Verify that providing a cap for the number of BEST_EFFORT tasks to the | |
| // constructor of TaskTracker is compatible with using an execution fence. | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| WillScheduleBestEffortSequenceWithMaxBestEffortSequencesAndFence) { | |
| constexpr int kMaxNumScheduledBestEffortSequences = 2; | |
| TaskTracker tracker("Test", kMaxNumScheduledBestEffortSequences); | |
| tracker.SetExecutionFenceEnabled(true); | |
| tracker.SetExecutionFenceEnabled(false); | |
| TestWillScheduleBestEffortSequenceWithMaxBestEffortSequences( | |
| kMaxNumScheduledBestEffortSequences, tracker); | |
| } | |
| namespace { | |
| void SetBool(bool* arg) { | |
| ASSERT_TRUE(arg); | |
| EXPECT_FALSE(*arg); | |
| *arg = true; | |
| } | |
| } // namespace | |
| // Verify that enabling the ScopedExecutionFence will prevent | |
| // WillScheduleSequence() returning sequence. | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| WillScheduleSequenceWithScopedExecutionFence) { | |
| TaskTraits default_traits = {}; | |
| Task task_a(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_a, default_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_a = | |
| test::CreateSequenceWithTask(std::move(task_a), default_traits); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; | |
| EXPECT_EQ(sequence_a, tracker_.WillScheduleSequence( | |
| sequence_a, &never_notified_observer)); | |
| // Verify that WillScheduleSequence() returns nullptr for foreground sequence | |
| // when the ScopedExecutionFence is enabled. | |
| tracker_.SetExecutionFenceEnabled(true); | |
| bool task_b_1_did_run = false; | |
| Task task_b_1(FROM_HERE, BindOnce(&SetBool, Unretained(&task_b_1_did_run)), | |
| TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_b_1, default_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_b = | |
| test::CreateSequenceWithTask(std::move(task_b_1), default_traits); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> observer_b_1; | |
| EXPECT_EQ(0, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::BEST_EFFORT)); | |
| EXPECT_FALSE(tracker_.WillScheduleSequence(sequence_b, &observer_b_1)); | |
| EXPECT_EQ(1, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::USER_VISIBLE)); | |
| bool task_b_2_did_run = false; | |
| Task task_b_2(FROM_HERE, BindOnce(&SetBool, Unretained(&task_b_2_did_run)), | |
| TimeDelta()); | |
| TaskTraits best_effort_traits = TaskTraits(TaskPriority::BEST_EFFORT); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_b_2, best_effort_traits.shutdown_behavior())); | |
| sequence_b->PushTask(std::move(task_b_2)); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> observer_b_2; | |
| EXPECT_EQ(nullptr, tracker_.WillScheduleSequence(sequence_b, &observer_b_2)); | |
| // The TaskPriority of the Sequence is unchanged by posting new tasks to it. | |
| EXPECT_EQ(2, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::USER_VISIBLE)); | |
| // Verify that WillScheduleSequence() returns nullptr for best-effort sequence | |
| // when the ScopedExecutionFence is enabled. | |
| bool task_c_did_run = false; | |
| Task task_c(FROM_HERE, BindOnce(&SetBool, Unretained(&task_c_did_run)), | |
| TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_c, best_effort_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_c = | |
| test::CreateSequenceWithTask(std::move(task_c), best_effort_traits); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> observer_c; | |
| EXPECT_EQ(nullptr, tracker_.WillScheduleSequence(sequence_c, &observer_c)); | |
| EXPECT_EQ(1, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::BEST_EFFORT)); | |
| // Verifies that the sequences preempted when the fence is on are rescheduled | |
| // right after the fence is released. | |
| EXPECT_CALL(observer_b_1, MockOnCanScheduleSequence(sequence_b.get())); | |
| EXPECT_CALL(observer_b_2, MockOnCanScheduleSequence(sequence_b.get())); | |
| EXPECT_CALL(observer_c, MockOnCanScheduleSequence(sequence_c.get())); | |
| tracker_.SetExecutionFenceEnabled(false); | |
| testing::Mock::VerifyAndClear(&observer_b_1); | |
| testing::Mock::VerifyAndClear(&observer_b_2); | |
| testing::Mock::VerifyAndClear(&observer_c); | |
| EXPECT_EQ(0, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::USER_VISIBLE)); | |
| EXPECT_EQ(0, tracker_.GetPreemptedSequenceCountForTesting( | |
| TaskPriority::BEST_EFFORT)); | |
| // Runs the sequences and verifies the tasks are done. | |
| EXPECT_FALSE( | |
| tracker_.RunAndPopNextTask(sequence_a, &never_notified_observer)); | |
| EXPECT_FALSE(task_b_1_did_run); | |
| EXPECT_EQ(sequence_b, tracker_.RunAndPopNextTask(sequence_b, &observer_b_1)); | |
| EXPECT_TRUE(task_b_1_did_run); | |
| EXPECT_FALSE(task_b_2_did_run); | |
| EXPECT_FALSE(tracker_.RunAndPopNextTask(sequence_b, &observer_b_2)); | |
| EXPECT_TRUE(task_b_2_did_run); | |
| EXPECT_FALSE(task_c_did_run); | |
| EXPECT_FALSE(tracker_.RunAndPopNextTask(sequence_c, &observer_c)); | |
| EXPECT_TRUE(task_c_did_run); | |
| // Verify that WillScheduleSequence() returns the sequence when the | |
| // ScopedExecutionFence isn't enabled. | |
| Task task_d(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker_.WillPostTask(&task_d, default_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_d = | |
| test::CreateSequenceWithTask(std::move(task_d), default_traits); | |
| EXPECT_EQ(sequence_d, tracker_.WillScheduleSequence( | |
| sequence_d, &never_notified_observer)); | |
| } | |
| // Verify that RunAndPopNextTask() doesn't reschedule the best-effort sequence | |
| // it was assigned if there is a preempted best-effort sequence with an earlier | |
| // sequence time (compared to the next task in the sequence assigned to | |
| // RunAndPopNextTask()). | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| RunNextBestEffortTaskWithEarlierPendingBestEffortTask) { | |
| constexpr int kMaxNumScheduledBestEffortSequences = 1; | |
| TaskTracker tracker("Test", kMaxNumScheduledBestEffortSequences); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; | |
| TaskTraits best_effort_traits = TaskTraits(TaskPriority::BEST_EFFORT); | |
| // Simulate posting a best-effort task and scheduling the associated sequence. | |
| // This should succeed. | |
| bool task_a_1_did_run = false; | |
| Task task_a_1(FROM_HERE, BindOnce(&SetBool, Unretained(&task_a_1_did_run)), | |
| TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker.WillPostTask(&task_a_1, best_effort_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_a = | |
| test::CreateSequenceWithTask(std::move(task_a_1), best_effort_traits); | |
| EXPECT_EQ(sequence_a, | |
| tracker.WillScheduleSequence(sequence_a, &never_notified_observer)); | |
| // Simulate posting an extra best-effort task and scheduling the associated | |
| // sequence. This should fail because the maximum number of best-effort | |
| // sequences that can be scheduled concurrently is already reached. | |
| bool task_b_1_did_run = false; | |
| Task task_b_1(FROM_HERE, BindOnce(&SetBool, Unretained(&task_b_1_did_run)), | |
| TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker.WillPostTask(&task_b_1, best_effort_traits.shutdown_behavior())); | |
| scoped_refptr<Sequence> sequence_b = | |
| test::CreateSequenceWithTask(std::move(task_b_1), best_effort_traits); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> task_b_1_observer; | |
| EXPECT_FALSE(tracker.WillScheduleSequence(sequence_b, &task_b_1_observer)); | |
| // Wait to be sure that the sequence time of |task_a_2| is after the sequenced | |
| // time of |task_b_1|. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| // Post an extra best-effort task in |sequence_a|. | |
| bool task_a_2_did_run = false; | |
| Task task_a_2(FROM_HERE, BindOnce(&SetBool, Unretained(&task_a_2_did_run)), | |
| TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker.WillPostTask(&task_a_2, best_effort_traits.shutdown_behavior())); | |
| sequence_a->PushTask(std::move(task_a_2)); | |
| // Run the first task in |sequence_a|. RunAndPopNextTask() should return | |
| // nullptr since |sequence_a| can't be rescheduled immediately. | |
| // |task_b_1_observer| should be notified that |sequence_b| can be scheduled. | |
| testing::StrictMock<MockCanScheduleSequenceObserver> task_a_2_observer; | |
| EXPECT_CALL(task_b_1_observer, MockOnCanScheduleSequence(sequence_b.get())); | |
| EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_a, &task_a_2_observer)); | |
| testing::Mock::VerifyAndClear(&task_b_1_observer); | |
| EXPECT_TRUE(task_a_1_did_run); | |
| // Run the first task in |sequence_b|. RunAndPopNextTask() should return | |
| // nullptr since |sequence_b| is empty after popping a task from it. | |
| // |task_a_2_observer| should be notified that |sequence_a| can be | |
| // scheduled. | |
| EXPECT_CALL(task_a_2_observer, MockOnCanScheduleSequence(sequence_a.get())); | |
| EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_b, &never_notified_observer)); | |
| testing::Mock::VerifyAndClear(&task_a_2_observer); | |
| EXPECT_TRUE(task_b_1_did_run); | |
| // Run the first task in |sequence_a|. RunAndPopNextTask() should return | |
| // nullptr since |sequence_b| is empty after popping a task from it. No | |
| // observer should be notified. | |
| EXPECT_FALSE(tracker.RunAndPopNextTask(sequence_a, &never_notified_observer)); | |
| EXPECT_TRUE(task_a_2_did_run); | |
| } | |
| // Verify that preempted best-effort sequences are scheduled when shutdown | |
| // starts. | |
| TEST_F(TaskSchedulerTaskTrackerTest, | |
| SchedulePreemptedBestEffortSequencesOnShutdown) { | |
| constexpr int kMaxNumScheduledBestEffortSequences = 0; | |
| TaskTracker tracker("Test", kMaxNumScheduledBestEffortSequences); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> observer; | |
| // Simulate scheduling sequences. TaskTracker should prevent this. | |
| std::vector<scoped_refptr<Sequence>> preempted_sequences; | |
| for (int i = 0; i < 3; ++i) { | |
| Task task(FROM_HERE, DoNothing(), TimeDelta()); | |
| EXPECT_TRUE( | |
| tracker.WillPostTask(&task, TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| scoped_refptr<Sequence> sequence = test::CreateSequenceWithTask( | |
| std::move(task), TaskTraits(TaskPriority::BEST_EFFORT, | |
| TaskShutdownBehavior::BLOCK_SHUTDOWN)); | |
| EXPECT_FALSE(tracker.WillScheduleSequence(sequence, &observer)); | |
| preempted_sequences.push_back(std::move(sequence)); | |
| // Wait to be sure that tasks have different |sequenced_time|. | |
| PlatformThread::Sleep(TestTimeouts::tiny_timeout()); | |
| } | |
| // Perform shutdown. Expect |preempted_sequences| to be scheduled in posting | |
| // order. | |
| { | |
| testing::InSequence in_sequence; | |
| for (auto& preempted_sequence : preempted_sequences) { | |
| EXPECT_CALL(observer, MockOnCanScheduleSequence(preempted_sequence.get())) | |
| .WillOnce(testing::Invoke([&tracker](Sequence* sequence) { | |
| // Run the task to unblock shutdown. | |
| tracker.RunAndPopNextTask(sequence, nullptr); | |
| })); | |
| } | |
| tracker.Shutdown(); | |
| } | |
| } | |
| namespace { | |
| class WaitAllowedTestThread : public SimpleThread { | |
| public: | |
| WaitAllowedTestThread() : SimpleThread("WaitAllowedTestThread") {} | |
| private: | |
| void Run() override { | |
| std::unique_ptr<StatisticsRecorder> recorder_for_testing = | |
| StatisticsRecorder::CreateTemporaryForTesting(); | |
| auto task_tracker = std::make_unique<TaskTracker>("Test"); | |
| // Waiting is allowed by default. Expect TaskTracker to disallow it before | |
| // running a task without the WithBaseSyncPrimitives() trait. | |
| internal::AssertBaseSyncPrimitivesAllowed(); | |
| Task task_without_sync_primitives( | |
| FROM_HERE, Bind([]() { | |
| EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); }); | |
| }), | |
| TimeDelta()); | |
| TaskTraits default_traits = {}; | |
| EXPECT_TRUE(task_tracker->WillPostTask(&task_without_sync_primitives, | |
| default_traits.shutdown_behavior())); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> | |
| never_notified_observer; | |
| auto sequence_without_sync_primitives = task_tracker->WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(task_without_sync_primitives), | |
| default_traits), | |
| &never_notified_observer); | |
| ASSERT_TRUE(sequence_without_sync_primitives); | |
| task_tracker->RunAndPopNextTask(std::move(sequence_without_sync_primitives), | |
| &never_notified_observer); | |
| // Disallow waiting. Expect TaskTracker to allow it before running a task | |
| // with the WithBaseSyncPrimitives() trait. | |
| ThreadRestrictions::DisallowWaiting(); | |
| Task task_with_sync_primitives( | |
| FROM_HERE, Bind([]() { | |
| // Shouldn't fail. | |
| internal::AssertBaseSyncPrimitivesAllowed(); | |
| }), | |
| TimeDelta()); | |
| TaskTraits traits_with_sync_primitives = | |
| TaskTraits(WithBaseSyncPrimitives()); | |
| EXPECT_TRUE(task_tracker->WillPostTask( | |
| &task_with_sync_primitives, | |
| traits_with_sync_primitives.shutdown_behavior())); | |
| auto sequence_with_sync_primitives = task_tracker->WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(task_with_sync_primitives), | |
| traits_with_sync_primitives), | |
| &never_notified_observer); | |
| ASSERT_TRUE(sequence_with_sync_primitives); | |
| task_tracker->RunAndPopNextTask(std::move(sequence_with_sync_primitives), | |
| &never_notified_observer); | |
| ScopedAllowBaseSyncPrimitivesForTesting | |
| allow_wait_in_task_tracker_destructor; | |
| task_tracker.reset(); | |
| } | |
| DISALLOW_COPY_AND_ASSIGN(WaitAllowedTestThread); | |
| }; | |
| } // namespace | |
| // Verify that AssertIOAllowed() succeeds only for a WithBaseSyncPrimitives() | |
| // task. | |
| TEST(TaskSchedulerTaskTrackerWaitAllowedTest, WaitAllowed) { | |
| // Run the test on the separate thread since it is not possible to reset the | |
| // "wait allowed" bit of a thread without being a friend of | |
| // ThreadRestrictions. | |
| testing::GTEST_FLAG(death_test_style) = "threadsafe"; | |
| WaitAllowedTestThread wait_allowed_test_thread; | |
| wait_allowed_test_thread.Start(); | |
| wait_allowed_test_thread.Join(); | |
| } | |
| // Verify that TaskScheduler.TaskLatency.* histograms are correctly recorded | |
| // when a task runs. | |
| TEST(TaskSchedulerTaskTrackerHistogramTest, TaskLatency) { | |
| auto statistics_recorder = StatisticsRecorder::CreateTemporaryForTesting(); | |
| TaskTracker tracker("Test"); | |
| testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; | |
| struct { | |
| const TaskTraits traits; | |
| const char* const expected_histogram; | |
| } static constexpr kTests[] = { | |
| {{TaskPriority::BEST_EFFORT}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "BackgroundTaskPriority"}, | |
| {{MayBlock(), TaskPriority::BEST_EFFORT}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "BackgroundTaskPriority_MayBlock"}, | |
| {{WithBaseSyncPrimitives(), TaskPriority::BEST_EFFORT}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "BackgroundTaskPriority_MayBlock"}, | |
| {{TaskPriority::USER_VISIBLE}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserVisibleTaskPriority"}, | |
| {{MayBlock(), TaskPriority::USER_VISIBLE}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserVisibleTaskPriority_MayBlock"}, | |
| {{WithBaseSyncPrimitives(), TaskPriority::USER_VISIBLE}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserVisibleTaskPriority_MayBlock"}, | |
| {{TaskPriority::USER_BLOCKING}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserBlockingTaskPriority"}, | |
| {{MayBlock(), TaskPriority::USER_BLOCKING}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserBlockingTaskPriority_MayBlock"}, | |
| {{WithBaseSyncPrimitives(), TaskPriority::USER_BLOCKING}, | |
| "TaskScheduler.TaskLatencyMicroseconds.Test." | |
| "UserBlockingTaskPriority_MayBlock"}}; | |
| for (const auto& test : kTests) { | |
| Task task(FROM_HERE, DoNothing(), TimeDelta()); | |
| ASSERT_TRUE(tracker.WillPostTask(&task, test.traits.shutdown_behavior())); | |
| HistogramTester tester; | |
| auto sequence = tracker.WillScheduleSequence( | |
| test::CreateSequenceWithTask(std::move(task), test.traits), | |
| &never_notified_observer); | |
| ASSERT_TRUE(sequence); | |
| tracker.RunAndPopNextTask(std::move(sequence), &never_notified_observer); | |
| tester.ExpectTotalCount(test.expected_histogram, 1); | |
| } | |
| } | |
| } // namespace internal | |
| } // namespace base |