diff options
Diffstat (limited to 'base/task_scheduler/task_tracker_unittest.cc')
-rw-r--r-- | base/task_scheduler/task_tracker_unittest.cc | 1364 |
1 files changed, 1364 insertions, 0 deletions
diff --git a/base/task_scheduler/task_tracker_unittest.cc b/base/task_scheduler/task_tracker_unittest.cc new file mode 100644 index 0000000000..159c9a9674 --- /dev/null +++ b/base/task_scheduler/task_tracker_unittest.cc @@ -0,0 +1,1364 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/task_scheduler/task_tracker.h" + +#include <stdint.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_scheduler/scheduler_lock.h" +#include "base/task_scheduler/task.h" +#include "base/task_scheduler/task_traits.h" +#include "base/task_scheduler/test_utils.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 "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, + Action action, + bool expect_post_succeeds) + : SimpleThread("ThreadPostingAndRunningTask"), + tracker_(tracker), + owned_task_(FROM_HERE, OnceClosure(), TaskTraits(), TimeDelta()), + task_(task), + 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, + Action action, + bool expect_post_succeeds) + : SimpleThread("ThreadPostingAndRunningTask"), + tracker_(tracker), + owned_task_(std::move(task)), + task_(&owned_task_), + 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_); + 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_)), + &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 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() = default; + + // Creates a task with |shutdown_behavior|. + Task CreateTask(TaskShutdownBehavior shutdown_behavior) { + return Task( + FROM_HERE, + Bind(&TaskSchedulerTaskTrackerTest::RunTaskCallback, Unretained(this)), + TaskTraits(shutdown_behavior), TimeDelta()); + } + + void DispatchAndRunTaskWithTracker(Task task) { + auto sequence = tracker_.WillScheduleSequence( + test::CreateSequenceWithTask(std::move(task)), + &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_; + } + + 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(GetParam())); + + // Inform |task_tracker_| that |task| will be posted. + EXPECT_TRUE(tracker_.WillPostTask(&task)); + + // Run the task. + EXPECT_EQ(0U, NumTasksExecuted()); + + DispatchAndRunTaskWithTracker(std::move(task)); + 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)), + TaskTraits(WithBaseSyncPrimitives(), GetParam()), TimeDelta()); + + // Inform |task_tracker_| that |blocked_task| will be posted. + EXPECT_TRUE(tracker_.WillPostTask(&blocked_task)); + + // Create a thread to run the task. Wait until the task starts running. + ThreadPostingAndRunningTask thread_running_task( + &tracker_, std::move(blocked_task), + 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(GetParam())); + EXPECT_TRUE(tracker_.WillPostTask(&task)); + + // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to + // block shutdown. + Task block_shutdown_task(CreateTask(TaskShutdownBehavior::BLOCK_SHUTDOWN)); + EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task)); + + // 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)); + 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)); + 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(GetParam())); + EXPECT_TRUE(tracker_.WillPostTask(&task)); + + // 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)); + 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)); + 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(TaskShutdownBehavior::BLOCK_SHUTDOWN)); + EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task)); + + // 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(GetParam())); + EXPECT_TRUE(tracker_.WillPostTask(&task)); + + // Run the BLOCK_SHUTDOWN task. + EXPECT_EQ(0U, NumTasksExecuted()); + DispatchAndRunTaskWithTracker(std::move(task)); + EXPECT_EQ(1U, NumTasksExecuted()); + } else { + // It shouldn't be allowed to post a non BLOCK_SHUTDOWN task. + Task task(CreateTask(GetParam())); + EXPECT_FALSE(tracker_.WillPostTask(&task)); + + // 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)); + EXPECT_EQ(GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN ? 2U : 1U, + NumTasksExecuted()); + WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, WillPostAfterShutdown) { + tracker_.Shutdown(); + + Task task(CreateTask(GetParam())); + + // |task_tracker_| shouldn't allow a task to be posted after shutdown. + EXPECT_FALSE(tracker_.WillPostTask(&task)); +} + +// 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), + TaskTraits(GetParam()), TimeDelta()); + EXPECT_TRUE(tracker_.WillPostTask(&task)); + + // 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)); + } else { + EXPECT_DCHECK_DEATH({ DispatchAndRunTaskWithTracker(std::move(task)); }); + } +} + +// 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(); + }), + TaskTraits(MayBlock(), GetParam()), TimeDelta()); + EXPECT_TRUE(tracker_.WillPostTask(&task_with_may_block)); + DispatchAndRunTaskWithTracker(std::move(task_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(); }); }), + TaskTraits(GetParam()), TimeDelta()); + EXPECT_TRUE(tracker_.WillPostTask(&task_without_may_block)); + DispatchAndRunTaskWithTracker(std::move(task_without_may_block)); +} + +static void RunTaskRunnerHandleVerificationTask(TaskTracker* tracker, + Task verify_task) { + // Pretend |verify_task| is posted to respect TaskTracker's contract. + EXPECT_TRUE(tracker->WillPostTask(&verify_task)); + + // 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)), + &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), + TaskTraits(GetParam()), TimeDelta()); + + RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task)); +} + +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())), + TaskTraits(GetParam()), TimeDelta()); + verify_task.sequenced_task_runner_ref = test_task_runner; + + RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task)); +} + +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())), + TaskTraits(GetParam()), TimeDelta()); + verify_task.single_thread_task_runner_ref = test_task_runner; + + RunTaskRunnerHandleVerificationTask(&tracker_, std::move(verify_task)); +} + +TEST_P(TaskSchedulerTaskTrackerTest, FlushPendingDelayedTask) { + Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta::FromDays(1)); + tracker_.WillPostTask(&delayed_task); + // FlushForTesting() should return even if the delayed task didn't run. + tracker_.FlushForTesting(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingDelayedTask) { + Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta::FromDays(1)); + tracker_.WillPostTask(&delayed_task); + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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)); + WAIT_FOR_ASYNC_FLUSH_RETURNED(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, FlushAsyncForTestingPendingUndelayedTask) { + Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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)); + event.Wait(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlush) { + Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&other_undelayed_task); + + // Run the first undelayed task. + DispatchAndRunTaskWithTracker(std::move(undelayed_task)); + + // 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)); + WAIT_FOR_ASYNC_FLUSH_RETURNED(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, PostTaskDuringFlushAsyncForTesting) { + Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&other_undelayed_task); + + // Run the first undelayed task. + DispatchAndRunTaskWithTracker(std::move(undelayed_task)); + + // 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)); + event.Wait(); +} + +TEST_P(TaskSchedulerTaskTrackerTest, RunDelayedTaskDuringFlush) { + // Simulate posting a delayed and an undelayed task. + Task delayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta::FromDays(1)); + tracker_.WillPostTask(&delayed_task); + Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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)); + + // 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)); + + // 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(), TaskTraits(GetParam()), + TimeDelta::FromDays(1)); + tracker_.WillPostTask(&delayed_task); + Task undelayed_task(FROM_HERE, DoNothing(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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)); + + // 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)); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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(), TaskTraits(GetParam()), + TimeDelta()); + tracker_.WillPostTask(&undelayed_task); + + // 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([]() {})); }); +} + +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>(); + + const SequenceToken sequence_token = sequence->token(); + Task task(FROM_HERE, Bind(&ExpectSequenceToken, sequence_token), TaskTraits(), + TimeDelta()); + tracker_.WillPostTask(&task); + + 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(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), + ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); + threads.back()->Start(); + + threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), + ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); + threads.back()->Start(); + + threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, CreateTask(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(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)); + tasks_skip_on_shutdown.push_back( + CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN)); + tasks_block_shutdown.push_back( + CreateTask(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], + ThreadPostingAndRunningTask::Action::WILL_POST, true)); + post_threads.back()->Start(); + + post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, &tasks_skip_on_shutdown[i], + ThreadPostingAndRunningTask::Action::WILL_POST, true)); + post_threads.back()->Start(); + + post_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, &tasks_block_shutdown[i], + 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]), + ThreadPostingAndRunningTask::Action::RUN, false)); + run_threads.back()->Start(); + + run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, std::move(tasks_skip_on_shutdown[i]), + ThreadPostingAndRunningTask::Action::RUN, false)); + run_threads.back()->Start(); + + run_threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, std::move(tasks_block_shutdown[i]), + 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(TaskShutdownBehavior::BLOCK_SHUTDOWN)); + EXPECT_TRUE(tracker_.WillPostTask(&block_shutdown_task)); + + // 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(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), + ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); + threads.back()->Start(); + + threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), + ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); + threads.back()->Start(); + + threads.push_back(std::make_unique<ThreadPostingAndRunningTask>( + &tracker_, CreateTask(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)); + 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) { + Task task_1(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta()); + EXPECT_TRUE(tracker_.WillPostTask(&task_1)); + Task task_2(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta()); + EXPECT_TRUE(tracker_.WillPostTask(&task_2)); + + scoped_refptr<Sequence> sequence = + test::CreateSequenceWithTask(std::move(task_1)); + sequence->PushTask(std::move(task_2)); + EXPECT_EQ(sequence, tracker_.WillScheduleSequence(sequence, nullptr)); + + EXPECT_EQ(sequence, tracker_.RunAndPopNextTask(sequence, nullptr)); +} + +// Verify that WillScheduleSequence() returns nullptr when it receives a +// background sequence and the maximum number of background sequences that can +// be scheduled concurrently is reached. Verify that an observer is notified +// when a background sequence can be scheduled (i.e. when one of the previously +// scheduled background sequences has run). +TEST_F(TaskSchedulerTaskTrackerTest, + WillScheduleBackgroundSequenceWithMaxBackgroundSequences) { + constexpr int kMaxNumScheduledBackgroundSequences = 2; + TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences); + + // Simulate posting |kMaxNumScheduledBackgroundSequences| background tasks + // and scheduling the associated sequences. This should succeed. + std::vector<scoped_refptr<Sequence>> scheduled_sequences; + testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; + for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) { + Task task(FROM_HERE, DoNothing(), TaskTraits(TaskPriority::BACKGROUND), + TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&task)); + scoped_refptr<Sequence> sequence = + test::CreateSequenceWithTask(std::move(task)); + EXPECT_EQ(sequence, + tracker.WillScheduleSequence(sequence, &never_notified_observer)); + scheduled_sequences.push_back(std::move(sequence)); + } + + // Simulate posting extra background tasks and scheduling the associated + // sequences. This should fail because the maximum number of background + // 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 < kMaxNumScheduledBackgroundSequences; ++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())), + TaskTraits(TaskPriority::BACKGROUND), TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&extra_task)); + extra_sequences.push_back( + test::CreateSequenceWithTask(std::move(extra_task))); + 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 < kMaxNumScheduledBackgroundSequences; ++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 < kMaxNumScheduledBackgroundSequences; ++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 { + +void SetBool(bool* arg) { + ASSERT_TRUE(arg); + EXPECT_FALSE(*arg); + *arg = true; +} + +} // namespace + +// Verify that RunAndPopNextTask() doesn't reschedule the background sequence it +// was assigned if there is a preempted background sequence with an earlier +// sequence time (compared to the next task in the sequence assigned to +// RunAndPopNextTask()). +TEST_F(TaskSchedulerTaskTrackerTest, + RunNextBackgroundTaskWithEarlierPendingBackgroundTask) { + constexpr int kMaxNumScheduledBackgroundSequences = 1; + TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences); + testing::StrictMock<MockCanScheduleSequenceObserver> never_notified_observer; + + // Simulate posting a background 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)), + TaskTraits(TaskPriority::BACKGROUND), TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&task_a_1)); + scoped_refptr<Sequence> sequence_a = + test::CreateSequenceWithTask(std::move(task_a_1)); + EXPECT_EQ(sequence_a, + tracker.WillScheduleSequence(sequence_a, &never_notified_observer)); + + // Simulate posting an extra background task and scheduling the associated + // sequence. This should fail because the maximum number of background + // 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)), + TaskTraits(TaskPriority::BACKGROUND), TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&task_b_1)); + scoped_refptr<Sequence> sequence_b = + test::CreateSequenceWithTask(std::move(task_b_1)); + 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 background 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)), + TaskTraits(TaskPriority::BACKGROUND), TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&task_a_2)); + 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 background sequences are scheduled when shutdown +// starts. +TEST_F(TaskSchedulerTaskTrackerTest, + SchedulePreemptedBackgroundSequencesOnShutdown) { + constexpr int kMaxNumScheduledBackgroundSequences = 0; + TaskTracker tracker("Test", kMaxNumScheduledBackgroundSequences); + 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(), + TaskTraits(TaskPriority::BACKGROUND, + TaskShutdownBehavior::BLOCK_SHUTDOWN), + TimeDelta()); + EXPECT_TRUE(tracker.WillPostTask(&task)); + scoped_refptr<Sequence> sequence = + test::CreateSequenceWithTask(std::move(task)); + 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 { + 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(); }); + }), + TaskTraits(), TimeDelta()); + EXPECT_TRUE(task_tracker->WillPostTask(&task_without_sync_primitives)); + testing::StrictMock<MockCanScheduleSequenceObserver> + never_notified_observer; + auto sequence_without_sync_primitives = task_tracker->WillScheduleSequence( + test::CreateSequenceWithTask(std::move(task_without_sync_primitives)), + &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(); + }), + TaskTraits(WithBaseSyncPrimitives()), TimeDelta()); + EXPECT_TRUE(task_tracker->WillPostTask(&task_with_sync_primitives)); + auto sequence_with_sync_primitives = task_tracker->WillScheduleSequence( + test::CreateSequenceWithTask(std::move(task_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::BACKGROUND}, + "TaskScheduler.TaskLatencyMicroseconds.Test." + "BackgroundTaskPriority"}, + {{MayBlock(), TaskPriority::BACKGROUND}, + "TaskScheduler.TaskLatencyMicroseconds.Test." + "BackgroundTaskPriority_MayBlock"}, + {{WithBaseSyncPrimitives(), TaskPriority::BACKGROUND}, + "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(), test.traits, TimeDelta()); + ASSERT_TRUE(tracker.WillPostTask(&task)); + + HistogramTester tester; + + auto sequence = tracker.WillScheduleSequence( + test::CreateSequenceWithTask(std::move(task)), + &never_notified_observer); + ASSERT_TRUE(sequence); + tracker.RunAndPopNextTask(std::move(sequence), &never_notified_observer); + tester.ExpectTotalCount(test.expected_histogram, 1); + } +} + +} // namespace internal +} // namespace base |