// 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 #include #include #include #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) 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 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 { 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 never_notified_observer_; private: void RunTaskCallback() { AutoSchedulerLock auto_lock(lock_); ++num_tasks_executed_; } std::unique_ptr thread_calling_shutdown_; std::unique_ptr 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 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 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 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 = MakeRefCounted(); 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> threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { threads.push_back(std::make_unique( &tracker_, CreateTask(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true)); threads.back()->Start(); threads.push_back(std::make_unique( &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 tasks_continue_on_shutdown; std::vector tasks_skip_on_shutdown; std::vector 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> post_threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { post_threads.push_back(std::make_unique( &tracker_, &tasks_continue_on_shutdown[i], ThreadPostingAndRunningTask::Action::WILL_POST, true)); post_threads.back()->Start(); post_threads.push_back(std::make_unique( &tracker_, &tasks_skip_on_shutdown[i], ThreadPostingAndRunningTask::Action::WILL_POST, true)); post_threads.back()->Start(); post_threads.push_back(std::make_unique( &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> run_threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { run_threads.push_back(std::make_unique( &tracker_, std::move(tasks_continue_on_shutdown[i]), ThreadPostingAndRunningTask::Action::RUN, false)); run_threads.back()->Start(); run_threads.push_back(std::make_unique( &tracker_, std::move(tasks_skip_on_shutdown[i]), ThreadPostingAndRunningTask::Action::RUN, false)); run_threads.back()->Start(); run_threads.push_back(std::make_unique( &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> threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { threads.push_back(std::make_unique( &tracker_, CreateTask(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, CreateTask(TaskShutdownBehavior::SKIP_ON_SHUTDOWN), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false)); threads.back()->Start(); threads.push_back(std::make_unique( &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 = 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> scheduled_sequences; testing::StrictMock 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 = 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> extra_tasks_did_run; std::vector< std::unique_ptr>> extra_observers; std::vector> extra_sequences; for (int i = 0; i < kMaxNumScheduledBackgroundSequences; ++i) { extra_tasks_did_run.push_back(std::make_unique()); 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>()); 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 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_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_b = test::CreateSequenceWithTask(std::move(task_b_1)); testing::StrictMock 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 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 observer; // Simulate scheduling sequences. TaskTracker should prevent this. std::vector> 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 = 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("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 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 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