// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/task/thread_pool/task_tracker.h" #include #include #include #include #include "base/barrier_closure.h" #include "base/check_op.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram_base.h" #include "base/metrics/histogram_samples.h" #include "base/sequence_token.h" #include "base/synchronization/atomic_flag.h" #include "base/task/common/checked_lock.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.h" #include "base/task/task_traits.h" #include "base/task/thread_pool/task.h" #include "base/task/thread_pool/test_utils.h" #include "base/test/bind.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/test/test_waitable_event.h" #include "base/threading/platform_thread.h" #include "base/threading/scoped_blocking_call.h" #include "base/threading/simple_thread.h" #include "base/threading/thread_restrictions.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; // Invokes a closure asynchronously. class CallbackThread : public SimpleThread { public: explicit CallbackThread(OnceClosure closure) : SimpleThread("CallbackThread"), closure_(std::move(closure)) {} CallbackThread(const CallbackThread&) = delete; CallbackThread& operator=(const CallbackThread&) = delete; // Returns true once the callback returns. bool has_returned() { return has_returned_.IsSet(); } private: void Run() override { std::move(closure_).Run(); has_returned_.Set(); } OnceClosure closure_; AtomicFlag has_returned_; }; class ThreadPostingAndRunningTask : public SimpleThread { public: enum class Action { WILL_POST, RUN, WILL_POST_AND_RUN, }; // |action| must be either WILL_POST or WILL_POST_AND_RUN. // |task| will be pushed to |sequence| and |sequence| will be registered. If // |action| is WILL_POST_AND_RUN, a task from |sequence| will run. ThreadPostingAndRunningTask(TaskTracker* tracker, scoped_refptr sequence, Action action, bool expect_post_succeeds, Task task) : SimpleThread("ThreadPostingAndRunningTask"), tracker_(tracker), task_(std::move(task)), sequence_(std::move(sequence)), action_(action), expect_post_succeeds_(expect_post_succeeds) { EXPECT_TRUE(task_.task); EXPECT_TRUE(sequence_); EXPECT_NE(Action::RUN, action_); } // A task from |task_source| will run. ThreadPostingAndRunningTask(TaskTracker* tracker, RegisteredTaskSource task_source) : SimpleThread("ThreadPostingAndRunningTask"), tracker_(tracker), task_source_(std::move(task_source)), action_(Action::RUN), expect_post_succeeds_(false) { EXPECT_TRUE(task_source_); } ThreadPostingAndRunningTask(const ThreadPostingAndRunningTask&) = delete; ThreadPostingAndRunningTask& operator=(const ThreadPostingAndRunningTask&) = delete; RegisteredTaskSource TakeTaskSource() { return std::move(task_source_); } private: void Run() override { bool post_and_queue_succeeded = true; if (action_ == Action::WILL_POST || action_ == Action::WILL_POST_AND_RUN) { EXPECT_TRUE(task_.task); post_and_queue_succeeded = tracker_->WillPostTask(&task_, sequence_->shutdown_behavior()); { auto transaction = sequence_->BeginTransaction(); transaction.WillPushImmediateTask(); transaction.PushImmediateTask(std::move(task_)); } task_source_ = tracker_->RegisterTaskSource(std::move(sequence_)); post_and_queue_succeeded &= !!task_source_; EXPECT_EQ(expect_post_succeeds_, post_and_queue_succeeded); } if (post_and_queue_succeeded && (action_ == Action::RUN || action_ == Action::WILL_POST_AND_RUN)) { EXPECT_TRUE(task_source_); task_source_.WillRunTask(); // Expect RunAndPopNextTask to return nullptr since |sequence| is empty // after popping a task from it. EXPECT_FALSE(tracker_->RunAndPopNextTask(std::move(task_source_))); } } const raw_ptr tracker_; Task task_; scoped_refptr sequence_; RegisteredTaskSource task_source_; const Action action_; const bool expect_post_succeeds_; }; class ThreadPoolTaskTrackerTest : public testing::TestWithParam { public: ThreadPoolTaskTrackerTest(const ThreadPoolTaskTrackerTest&) = delete; ThreadPoolTaskTrackerTest& operator=(const ThreadPoolTaskTrackerTest&) = delete; protected: ThreadPoolTaskTrackerTest() = default; // Creates a task. Task CreateTask() { return Task( FROM_HERE, BindOnce(&ThreadPoolTaskTrackerTest::RunTaskCallback, Unretained(this)), TimeTicks::Now(), TimeDelta()); } RegisteredTaskSource WillPostTaskAndQueueTaskSource( Task task, const TaskTraits& traits) { if (!tracker_.WillPostTask(&task, traits.shutdown_behavior())) return nullptr; auto sequence = test::CreateSequenceWithTask(std::move(task), traits); return tracker_.RegisterTaskSource(std::move(sequence)); } RegisteredTaskSource RunAndPopNextTask(RegisteredTaskSource task_source) { task_source.WillRunTask(); return tracker_.RunAndPopNextTask(std::move(task_source)); } // Calls tracker_->CompleteShutdown() on a new thread and expects it to block. void ExpectAsyncCompleteShutdownBlocks() { ASSERT_FALSE(thread_calling_shutdown_); ASSERT_TRUE(tracker_.HasShutdownStarted()); thread_calling_shutdown_ = std::make_unique( BindOnce(&TaskTracker::CompleteShutdown, Unretained(&tracker_))); thread_calling_shutdown_->Start(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VerifyAsyncShutdownInProgress(); } 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() { threads_calling_flush_.push_back(std::make_unique( BindOnce(&TaskTracker::FlushForTesting, Unretained(&tracker_)))); threads_calling_flush_.back()->Start(); } void WaitForAsyncFlushesReturned() { ASSERT_GE(threads_calling_flush_.size(), 1U); for (auto& thread_calling_flush : threads_calling_flush_) { thread_calling_flush->Join(); EXPECT_TRUE(thread_calling_flush->has_returned()); } } void VerifyAsyncFlushesInProgress() { ASSERT_GE(threads_calling_flush_.size(), 1U); for (auto& thread_calling_flush : threads_calling_flush_) { EXPECT_FALSE(thread_calling_flush->has_returned()); } } size_t NumTasksExecuted() { CheckedAutoLock auto_lock(lock_); return num_tasks_executed_; } TaskTracker tracker_; private: void RunTaskCallback() { CheckedAutoLock auto_lock(lock_); ++num_tasks_executed_; } std::unique_ptr thread_calling_shutdown_; std::vector> threads_calling_flush_; // Synchronizes accesses to |num_tasks_executed_|. CheckedLock lock_; size_t num_tasks_executed_ = 0; }; #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_FLUSHES_RETURNED() \ do { \ SCOPED_TRACE(""); \ WaitForAsyncFlushesReturned(); \ } while (false) #define VERIFY_ASYNC_FLUSHES_IN_PROGRESS() \ do { \ SCOPED_TRACE(""); \ VerifyAsyncFlushesInProgress(); \ } while (false) } // namespace TEST_P(ThreadPoolTaskTrackerTest, WillPostAndRunBeforeShutdown) { Task task(CreateTask()); // Inform |task_tracker_| that |task| will be posted. EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); // Run the task. EXPECT_EQ(0U, NumTasksExecuted()); test::QueueAndRunTaskSource( &tracker_, test::CreateSequenceWithTask(std::move(task), {GetParam()})); EXPECT_EQ(1U, NumTasksExecuted()); // Shutdown() shouldn't block. test::ShutdownTaskTracker(&tracker_); } TEST_P(ThreadPoolTaskTrackerTest, WillPostAndRunLongTaskBeforeShutdown) { // Create a task that signals |task_running| and blocks until |task_barrier| // is signaled. TestWaitableEvent task_running; TestWaitableEvent task_barrier; Task blocked_task(FROM_HERE, BindLambdaForTesting([&] { task_running.Signal(); task_barrier.Wait(); }), TimeTicks::Now(), TimeDelta()); // Inform |task_tracker_| that |blocked_task| will be posted. auto sequence = WillPostTaskAndQueueTaskSource(std::move(blocked_task), {GetParam()}); EXPECT_TRUE(sequence); // Create a thread to run the task. Wait until the task starts running. ThreadPostingAndRunningTask thread_running_task(&tracker_, std::move(sequence)); thread_running_task.Start(); task_running.Wait(); // Initiate shutdown after the task has started to run. tracker_.StartShutdown(); if (GetParam() == TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) { // Shutdown should complete even with a CONTINUE_ON_SHUTDOWN in progress. tracker_.CompleteShutdown(); } else { // Shutdown should block with any non CONTINUE_ON_SHUTDOWN task in progress. ExpectAsyncCompleteShutdownBlocks(); } // 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(); } // Posting a BLOCK_SHUTDOWN task after shutdown must be allowed from a // CONTINUE_ON_SHUTDOWN. Ref. https://crbug.com/1499644#c9 - #c16. // Note: This test can't be TEST_P as non-CONTINUE_ON_SHUTDOWN `poster` would // hang in CompleteShutdown(). TEST_F(ThreadPoolTaskTrackerTest, PostAfterShutdownFromContinueOnShutdown) { // Dummy. Task task{CreateTask()}; // Create a task that verifies the properties of this test. TestWaitableEvent task_running; TestWaitableEvent task_barrier; Task poster(FROM_HERE, BindLambdaForTesting([&] { task_running.Signal(); task_barrier.Wait(); // No death when posting BLOCK_SHUTDOWN from // CONTINUE_ON_SHUTDOWN. EXPECT_TRUE(tracker_.IsShutdownComplete()); EXPECT_FALSE(tracker_.WillPostTask( &task, TaskShutdownBehavior::BLOCK_SHUTDOWN)); }), TimeTicks::Now(), TimeDelta()); // Inform |task_tracker_| that |blocked_task| will be posted. auto sequence = WillPostTaskAndQueueTaskSource( std::move(poster), TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN); EXPECT_TRUE(sequence); // Create a thread to run the task. Wait until the task starts running. ThreadPostingAndRunningTask thread_running_task(&tracker_, std::move(sequence)); thread_running_task.Start(); task_running.Wait(); // Fully shutdown `tracker_` Make sure it's complete before releasing the task // to perform its test for CONTINUE_ON_SHUTDOWN. test::ShutdownTaskTracker(&tracker_); // Unblock the task and wait for it to perform its test. task_barrier.Signal(); thread_running_task.Join(); } // Verify that an undelayed task whose sequence wasn't queued does not block // shutdown, regardless of its shutdown behavior. TEST_P(ThreadPoolTaskTrackerTest, WillPostBeforeShutdownQueueDuringShutdown) { // Simulate posting a undelayed task. Task task{CreateTask()}; EXPECT_TRUE(tracker_.WillPostTask(&task, GetParam())); auto sequence = test::CreateSequenceWithTask(std::move(task), {GetParam()}); // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to // block shutdown. auto block_shutdown_sequence = WillPostTaskAndQueueTaskSource( CreateTask(), {TaskShutdownBehavior::BLOCK_SHUTDOWN}); EXPECT_TRUE(block_shutdown_sequence); // Start shutdown and try to complete it asynchronously. tracker_.StartShutdown(); ExpectAsyncCompleteShutdownBlocks(); const bool should_run = GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN; if (should_run) { test::QueueAndRunTaskSource(&tracker_, std::move(sequence)); EXPECT_EQ(1U, NumTasksExecuted()); VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); } else { EXPECT_FALSE(tracker_.RegisterTaskSource(std::move(sequence))); } // Unblock shutdown by running the remaining BLOCK_SHUTDOWN task. RunAndPopNextTask(std::move(block_shutdown_sequence)); EXPECT_EQ(should_run ? 2U : 1U, NumTasksExecuted()); WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); } TEST_P(ThreadPoolTaskTrackerTest, WillPostBeforeShutdownRunDuringShutdown) { // Inform |task_tracker_| that a task will be posted. auto sequence = WillPostTaskAndQueueTaskSource(CreateTask(), {GetParam()}); EXPECT_TRUE(sequence); // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to // block shutdown. auto block_shutdown_sequence = WillPostTaskAndQueueTaskSource( CreateTask(), {TaskShutdownBehavior::BLOCK_SHUTDOWN}); EXPECT_TRUE(block_shutdown_sequence); // Start shutdown and try to complete it asynchronously. tracker_.StartShutdown(); ExpectAsyncCompleteShutdownBlocks(); // 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; RunAndPopNextTask(std::move(sequence)); EXPECT_EQ(should_run ? 1U : 0U, NumTasksExecuted()); VERIFY_ASYNC_SHUTDOWN_IN_PROGRESS(); // Unblock shutdown by running the remaining BLOCK_SHUTDOWN task. RunAndPopNextTask(std::move(block_shutdown_sequence)); EXPECT_EQ(should_run ? 2U : 1U, NumTasksExecuted()); WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); } TEST_P(ThreadPoolTaskTrackerTest, WillPostBeforeShutdownRunAfterShutdown) { // Inform |task_tracker_| that a task will be posted. auto sequence = WillPostTaskAndQueueTaskSource(CreateTask(), {GetParam()}); EXPECT_TRUE(sequence); // Start shutdown. tracker_.StartShutdown(); EXPECT_EQ(0U, NumTasksExecuted()); if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) { // Verify that CompleteShutdown() blocks. ExpectAsyncCompleteShutdownBlocks(); // Run the task to unblock shutdown. RunAndPopNextTask(std::move(sequence)); 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 { tracker_.CompleteShutdown(); // The task shouldn't be allowed to run after shutdown. RunAndPopNextTask(std::move(sequence)); EXPECT_EQ(0U, NumTasksExecuted()); } } TEST_P(ThreadPoolTaskTrackerTest, WillPostAndRunDuringShutdown) { // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to // block shutdown. auto block_shutdown_sequence = WillPostTaskAndQueueTaskSource( CreateTask(), {TaskShutdownBehavior::BLOCK_SHUTDOWN}); EXPECT_TRUE(block_shutdown_sequence); // Start shutdown. tracker_.StartShutdown(); if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) { // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted. auto sequence = WillPostTaskAndQueueTaskSource(CreateTask(), {GetParam()}); EXPECT_TRUE(sequence); // Run the BLOCK_SHUTDOWN task. EXPECT_EQ(0U, NumTasksExecuted()); RunAndPopNextTask(std::move(sequence)); EXPECT_EQ(1U, NumTasksExecuted()); } else { // It shouldn't be allowed to post a non BLOCK_SHUTDOWN task. auto sequence = WillPostTaskAndQueueTaskSource(CreateTask(), {GetParam()}); EXPECT_FALSE(sequence); // Don't try to run the task, because it wasn't allowed to be posted. } // Verify that CompleteShutdown() blocks. ExpectAsyncCompleteShutdownBlocks(); // Unblock shutdown by running |block_shutdown_task|. RunAndPopNextTask(std::move(block_shutdown_sequence)); EXPECT_EQ(GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN ? 2U : 1U, NumTasksExecuted()); WAIT_FOR_ASYNC_SHUTDOWN_COMPLETED(); } TEST_P(ThreadPoolTaskTrackerTest, WillPostAfterShutdown) { test::ShutdownTaskTracker(&tracker_); Task task(CreateTask()); // |task_tracker_| shouldn't allow a task to be posted after shutdown. if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) { // When the task tracker is allowed to fizzle block shutdown tasks, // WillPostTask will return false and leak the task. tracker_.BeginFizzlingBlockShutdownTasks(); EXPECT_FALSE(tracker_.WillPostTask(&task, GetParam())); tracker_.EndFizzlingBlockShutdownTasks(); // If a BLOCK_SHUTDOWN task is posted after shutdown without explicitly // allowing BLOCK_SHUTDOWN task fizzling, WillPostTask DCHECKs to find // ordering bugs. EXPECT_DCHECK_DEATH(tracker_.WillPostTask(&task, GetParam())); } else { EXPECT_FALSE(tracker_.WillPostTask(&task, GetParam())); } } // Verify that BLOCK_SHUTDOWN and SKIP_ON_SHUTDOWN tasks can // AssertSingletonAllowed() but CONTINUE_ON_SHUTDOWN tasks can't. TEST_P(ThreadPoolTaskTrackerTest, SingletonAllowed) { const bool can_use_singletons = (GetParam() != TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN); Task task(FROM_HERE, BindOnce(&internal::AssertSingletonAllowed), TimeTicks::Now(), TimeDelta()); auto sequence = WillPostTaskAndQueueTaskSource(std::move(task), {GetParam()}); EXPECT_TRUE(sequence); // Running the task should fail iff the task isn't allowed to use singletons. if (can_use_singletons) { EXPECT_FALSE(RunAndPopNextTask(std::move(sequence))); } else { EXPECT_DCHECK_DEATH({ RunAndPopNextTask(std::move(sequence)); }); } } // Verify that AssertIOAllowed() succeeds only for a MayBlock() task. TEST_P(ThreadPoolTaskTrackerTest, IOAllowed) { // Allowed with MayBlock(). Task task_with_may_block(FROM_HERE, BindOnce([] { // Shouldn't fail. ScopedBlockingCall scope_blocking_call( FROM_HERE, BlockingType::WILL_BLOCK); }), TimeTicks::Now(), TimeDelta()); TaskTraits traits_with_may_block{MayBlock(), GetParam()}; auto sequence_with_may_block = WillPostTaskAndQueueTaskSource( std::move(task_with_may_block), traits_with_may_block); EXPECT_TRUE(sequence_with_may_block); RunAndPopNextTask(std::move(sequence_with_may_block)); // Disallowed in the absence of MayBlock(). Task task_without_may_block(FROM_HERE, BindOnce([] { EXPECT_DCHECK_DEATH({ ScopedBlockingCall scope_blocking_call( FROM_HERE, BlockingType::WILL_BLOCK); }); }), TimeTicks::Now(), TimeDelta()); TaskTraits traits_without_may_block = TaskTraits(GetParam()); auto sequence_without_may_block = WillPostTaskAndQueueTaskSource( std::move(task_without_may_block), traits_without_may_block); EXPECT_TRUE(sequence_without_may_block); RunAndPopNextTask(std::move(sequence_without_may_block)); } static void RunTaskRunnerCurrentDefaultHandleVerificationTask( TaskTracker* tracker, Task verify_task, TaskTraits traits, scoped_refptr task_runner, TaskSourceExecutionMode execution_mode) { // Pretend |verify_task| is posted to respect TaskTracker's contract. EXPECT_TRUE(tracker->WillPostTask(&verify_task, traits.shutdown_behavior())); // Confirm that the test conditions are right (no // task runner CurrentDefaultHandles set already). EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault()); EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault()); test::QueueAndRunTaskSource( tracker, test::CreateSequenceWithTask(std::move(verify_task), traits, std::move(task_runner), execution_mode)); // task runner CurrentDefaultHandle state is reset outside of task's scope. EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault()); EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault()); } static void VerifyNoTaskRunnerCurrentDefaultHandle() { EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault()); EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault()); } TEST_P(ThreadPoolTaskTrackerTest, 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(&VerifyNoTaskRunnerCurrentDefaultHandle), TimeTicks::Now(), TimeDelta()); RunTaskRunnerCurrentDefaultHandleVerificationTask( &tracker_, std::move(verify_task), TaskTraits(GetParam()), nullptr, TaskSourceExecutionMode::kParallel); } static void VerifySequencedTaskRunnerCurrentDefaultHandle( const SequencedTaskRunner* expected_task_runner) { EXPECT_FALSE(SingleThreadTaskRunner::HasCurrentDefault()); EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault()); EXPECT_EQ(expected_task_runner, SequencedTaskRunner::GetCurrentDefault()); } TEST_P(ThreadPoolTaskTrackerTest, SequencedTaskRunnerHasCurrentDefaultOnSequenced) { scoped_refptr test_task_runner(new TestSimpleTaskRunner); // Create a task that will verify that // SequencedTaskRunner::CurrentDefaultHandle 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(&VerifySequencedTaskRunnerCurrentDefaultHandle, Unretained(test_task_runner.get())), TimeTicks::Now(), TimeDelta()); RunTaskRunnerCurrentDefaultHandleVerificationTask( &tracker_, std::move(verify_task), TaskTraits(GetParam()), std::move(test_task_runner), TaskSourceExecutionMode::kSequenced); } static void VerifySingleThreadTaskRunnerCurrentDefaultHandle( const SingleThreadTaskRunner* expected_task_runner) { EXPECT_TRUE(SingleThreadTaskRunner::HasCurrentDefault()); // SequencedTaskRunner::CurrentDefaultHandle inherits // SingleThreadTaskRunner::CurrentDefaultHandle for thread. EXPECT_TRUE(SequencedTaskRunner::HasCurrentDefault()); EXPECT_EQ(expected_task_runner, SingleThreadTaskRunner::GetCurrentDefault()); } TEST_P(ThreadPoolTaskTrackerTest, SingleThreadTaskRunnerCurrentDefaultHandleIsSetOnSingleThreaded) { scoped_refptr test_task_runner( new TestSimpleTaskRunner); // Create a task that will verify that // SingleThreadTaskRunner::CurrentDefaultHandle 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(&VerifySingleThreadTaskRunnerCurrentDefaultHandle, Unretained(test_task_runner.get())), TimeTicks::Now(), TimeDelta()); RunTaskRunnerCurrentDefaultHandleVerificationTask( &tracker_, std::move(verify_task), TaskTraits(GetParam()), std::move(test_task_runner), TaskSourceExecutionMode::kSingleThread); } TEST_P(ThreadPoolTaskTrackerTest, FlushPendingDelayedTask) { Task delayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), Days(1)); tracker_.WillPostTask(&delayed_task, GetParam()); // FlushForTesting() should return even if the delayed task didn't run. tracker_.FlushForTesting(); } TEST_P(ThreadPoolTaskTrackerTest, FlushAsyncForTestingPendingDelayedTask) { Task delayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), Days(1)); tracker_.WillPostTask(&delayed_task, GetParam()); // FlushAsyncForTesting() should callback even if the delayed task didn't run. bool called_back = false; tracker_.FlushAsyncForTesting( BindOnce([](bool* called_back) { *called_back = true; }, Unretained(&called_back))); EXPECT_TRUE(called_back); } TEST_P(ThreadPoolTaskTrackerTest, FlushPendingUndelayedTask) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushForTesting() shouldn't return before the undelayed task runs. CallFlushFromAnotherThread(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // FlushForTesting() should return after the undelayed task runs. RunAndPopNextTask(std::move(undelayed_sequence)); WAIT_FOR_ASYNC_FLUSHES_RETURNED(); } TEST_P(ThreadPoolTaskTrackerTest, MultipleFlushes) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // Multiple flushes should all unwind after the task runs. CallFlushFromAnotherThread(); CallFlushFromAnotherThread(); CallFlushFromAnotherThread(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); RunAndPopNextTask(std::move(undelayed_sequence)); WAIT_FOR_ASYNC_FLUSHES_RETURNED(); } TEST_P(ThreadPoolTaskTrackerTest, FlushAsyncForTestingPendingUndelayedTask) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. TestWaitableEvent event; tracker_.FlushAsyncForTesting( BindOnce(&TestWaitableEvent::Signal, Unretained(&event))); EXPECT_FALSE(event.IsSignaled()); // FlushAsyncForTesting() should callback after the undelayed task runs. RunAndPopNextTask(std::move(undelayed_sequence)); event.Wait(); } TEST_P(ThreadPoolTaskTrackerTest, MultipleFlushAsyncForTesting) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); TestWaitableEvent three_callbacks_ran; auto on_flush_done = BarrierClosure( 3, BindOnce(&TestWaitableEvent::Signal, Unretained(&three_callbacks_ran))); tracker_.FlushAsyncForTesting(on_flush_done); tracker_.FlushAsyncForTesting(on_flush_done); tracker_.FlushAsyncForTesting(on_flush_done); EXPECT_FALSE(three_callbacks_ran.IsSignaled()); // FlushAsyncForTesting() should callback after the undelayed task runs. RunAndPopNextTask(std::move(undelayed_sequence)); three_callbacks_ran.Wait(); } TEST_P(ThreadPoolTaskTrackerTest, PostTaskDuringFlush) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushForTesting() shouldn't return before the undelayed task runs. CallFlushFromAnotherThread(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // Simulate posting another undelayed task. Task other_undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto other_undelayed_sequence = WillPostTaskAndQueueTaskSource( std::move(other_undelayed_task), {GetParam()}); // Run the first undelayed task. RunAndPopNextTask(std::move(undelayed_sequence)); // FlushForTesting() shouldn't return before the second undelayed task runs. PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // FlushForTesting() should return after the second undelayed task runs. RunAndPopNextTask(std::move(other_undelayed_sequence)); WAIT_FOR_ASYNC_FLUSHES_RETURNED(); } TEST_P(ThreadPoolTaskTrackerTest, PostTaskDuringFlushAsyncForTesting) { Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. TestWaitableEvent event; tracker_.FlushAsyncForTesting( BindOnce(&TestWaitableEvent::Signal, Unretained(&event))); EXPECT_FALSE(event.IsSignaled()); // Simulate posting another undelayed task. Task other_undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto other_undelayed_sequence = WillPostTaskAndQueueTaskSource( std::move(other_undelayed_task), {GetParam()}); // Run the first undelayed task. RunAndPopNextTask(std::move(undelayed_sequence)); // FlushAsyncForTesting() shouldn't callback before the second undelayed task // runs. EXPECT_FALSE(event.IsSignaled()); // FlushAsyncForTesting() should callback after the second undelayed task // runs. RunAndPopNextTask(std::move(other_undelayed_sequence)); event.Wait(); } TEST_P(ThreadPoolTaskTrackerTest, RunDelayedTaskDuringFlush) { // Simulate posting a delayed and an undelayed task. Task delayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), Days(1)); auto delayed_sequence = WillPostTaskAndQueueTaskSource(std::move(delayed_task), {GetParam()}); Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushForTesting() shouldn't return before the undelayed task runs. CallFlushFromAnotherThread(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // Run the delayed task. RunAndPopNextTask(std::move(delayed_sequence)); // FlushForTesting() shouldn't return since there is still a pending undelayed // task. PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // Run the undelayed task. RunAndPopNextTask(std::move(undelayed_sequence)); // FlushForTesting() should now ESreturn. WAIT_FOR_ASYNC_FLUSHES_RETURNED(); } TEST_P(ThreadPoolTaskTrackerTest, RunDelayedTaskDuringFlushAsyncForTesting) { // Simulate posting a delayed and an undelayed task. Task delayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), Days(1)); auto delayed_sequence = WillPostTaskAndQueueTaskSource(std::move(delayed_task), {GetParam()}); Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushAsyncForTesting() shouldn't callback before the undelayed task runs. TestWaitableEvent event; tracker_.FlushAsyncForTesting( BindOnce(&TestWaitableEvent::Signal, Unretained(&event))); EXPECT_FALSE(event.IsSignaled()); // Run the delayed task. RunAndPopNextTask(std::move(delayed_sequence)); // FlushAsyncForTesting() shouldn't callback since there is still a pending // undelayed task. EXPECT_FALSE(event.IsSignaled()); // Run the undelayed task. RunAndPopNextTask(std::move(undelayed_sequence)); // FlushAsyncForTesting() should now callback. event.Wait(); } TEST_P(ThreadPoolTaskTrackerTest, FlushAfterShutdown) { if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) return; // Simulate posting a task. Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); tracker_.WillPostTask(&undelayed_task, GetParam()); // Shutdown() should return immediately since there are no pending // BLOCK_SHUTDOWN tasks. test::ShutdownTaskTracker(&tracker_); // FlushForTesting() should return immediately after shutdown, even if an // undelayed task hasn't run. tracker_.FlushForTesting(); } TEST_P(ThreadPoolTaskTrackerTest, FlushAfterShutdownAsync) { if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) return; // Simulate posting a task. Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); tracker_.WillPostTask(&undelayed_task, GetParam()); // Shutdown() should return immediately since there are no pending // BLOCK_SHUTDOWN tasks. test::ShutdownTaskTracker(&tracker_); // 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(ThreadPoolTaskTrackerTest, ShutdownDuringFlush) { if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) return; // Simulate posting a task. Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushForTesting() shouldn't return before the undelayed task runs or // shutdown completes. CallFlushFromAnotherThread(); PlatformThread::Sleep(TestTimeouts::tiny_timeout()); VERIFY_ASYNC_FLUSHES_IN_PROGRESS(); // Shutdown() should return immediately since there are no pending // BLOCK_SHUTDOWN tasks. test::ShutdownTaskTracker(&tracker_); // FlushForTesting() should now return, even if an undelayed task hasn't run. WAIT_FOR_ASYNC_FLUSHES_RETURNED(); } TEST_P(ThreadPoolTaskTrackerTest, ShutdownDuringFlushAsyncForTesting) { if (GetParam() == TaskShutdownBehavior::BLOCK_SHUTDOWN) return; // Simulate posting a task. Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); auto undelayed_sequence = WillPostTaskAndQueueTaskSource(std::move(undelayed_task), {GetParam()}); // FlushAsyncForTesting() shouldn't callback before the undelayed task runs or // shutdown completes. TestWaitableEvent event; tracker_.FlushAsyncForTesting( BindOnce(&TestWaitableEvent::Signal, Unretained(&event))); EXPECT_FALSE(event.IsSignaled()); // Shutdown() should return immediately since there are no pending // BLOCK_SHUTDOWN tasks. test::ShutdownTaskTracker(&tracker_); // FlushAsyncForTesting() should now callback, even if an undelayed task // hasn't run. event.Wait(); } TEST_P(ThreadPoolTaskTrackerTest, PostTasksDoNotBlockShutdown) { // Simulate posting an undelayed task. Task undelayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); EXPECT_TRUE(tracker_.WillPostTask(&undelayed_task, GetParam())); // Since no sequence was queued, a call to Shutdown() should not hang. test::ShutdownTaskTracker(&tracker_); } // Verify that a delayed task does not block shutdown once it's run, regardless // of its shutdown behavior. TEST_P(ThreadPoolTaskTrackerTest, DelayedRunTasks) { // Simulate posting a delayed task. Task delayed_task(FROM_HERE, DoNothing(), TimeTicks::Now(), Days(1)); auto sequence = WillPostTaskAndQueueTaskSource(std::move(delayed_task), {GetParam()}); EXPECT_TRUE(sequence); RunAndPopNextTask(std::move(sequence)); // Since the delayed task doesn't block shutdown, a call to Shutdown() should // not hang. test::ShutdownTaskTracker(&tracker_); } INSTANTIATE_TEST_SUITE_P( ContinueOnShutdown, ThreadPoolTaskTrackerTest, ::testing::Values(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)); INSTANTIATE_TEST_SUITE_P( SkipOnShutdown, ThreadPoolTaskTrackerTest, ::testing::Values(TaskShutdownBehavior::SKIP_ON_SHUTDOWN)); INSTANTIATE_TEST_SUITE_P( BlockShutdown, ThreadPoolTaskTrackerTest, ::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(ThreadPoolTaskTrackerTest, CurrentSequenceToken) { scoped_refptr sequence = MakeRefCounted( TaskTraits(), nullptr, TaskSourceExecutionMode::kParallel); const SequenceToken sequence_token = sequence->token(); Task task(FROM_HERE, BindOnce(&ExpectSequenceToken, sequence_token), TimeTicks::Now(), TimeDelta()); tracker_.WillPostTask(&task, sequence->shutdown_behavior()); { Sequence::Transaction sequence_transaction(sequence->BeginTransaction()); sequence_transaction.WillPushImmediateTask(); sequence_transaction.PushImmediateTask(std::move(task)); EXPECT_NE(SequenceToken::GetForCurrentThread(), sequence_token); } test::QueueAndRunTaskSource(&tracker_, std::move(sequence)); EXPECT_NE(SequenceToken::GetForCurrentThread(), sequence_token); } TEST_F(ThreadPoolTaskTrackerTest, LoadWillPostAndRunBeforeShutdown) { // Post and run tasks asynchronously. std::vector> threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true, CreateTask())); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true, CreateTask())); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::BLOCK_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true, CreateTask())); 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. test::ShutdownTaskTracker(&tracker_); } TEST_F(ThreadPoolTaskTrackerTest, LoadWillPostBeforeShutdownAndRunDuringShutdown) { constexpr TaskTraits traits_continue_on_shutdown = TaskTraits(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN); constexpr TaskTraits traits_skip_on_shutdown = TaskTraits(TaskShutdownBehavior::SKIP_ON_SHUTDOWN); constexpr TaskTraits traits_block_shutdown = TaskTraits(TaskShutdownBehavior::BLOCK_SHUTDOWN); // Post tasks asynchronously. std::vector> post_threads; { std::vector> sequences_continue_on_shutdown; std::vector> sequences_skip_on_shutdown; std::vector> sequences_block_shutdown; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { sequences_continue_on_shutdown.push_back( MakeRefCounted(traits_continue_on_shutdown, nullptr, TaskSourceExecutionMode::kParallel)); sequences_skip_on_shutdown.push_back( MakeRefCounted(traits_skip_on_shutdown, nullptr, TaskSourceExecutionMode::kParallel)); sequences_block_shutdown.push_back(MakeRefCounted( traits_block_shutdown, nullptr, TaskSourceExecutionMode::kParallel)); } for (size_t i = 0; i < kLoadTestNumIterations; ++i) { post_threads.push_back(std::make_unique( &tracker_, sequences_continue_on_shutdown[i], ThreadPostingAndRunningTask::Action::WILL_POST, true, CreateTask())); post_threads.back()->Start(); post_threads.push_back(std::make_unique( &tracker_, sequences_skip_on_shutdown[i], ThreadPostingAndRunningTask::Action::WILL_POST, true, CreateTask())); post_threads.back()->Start(); post_threads.push_back(std::make_unique( &tracker_, sequences_block_shutdown[i], ThreadPostingAndRunningTask::Action::WILL_POST, true, CreateTask())); post_threads.back()->Start(); } } for (const auto& thread : post_threads) thread->Join(); // Start shutdown and try to complete shutdown asynchronously. tracker_.StartShutdown(); ExpectAsyncCompleteShutdownBlocks(); // Run tasks asynchronously. std::vector> run_threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { run_threads.push_back(std::make_unique( &tracker_, post_threads[i * 3]->TakeTaskSource())); run_threads.back()->Start(); run_threads.push_back(std::make_unique( &tracker_, post_threads[i * 3 + 1]->TakeTaskSource())); run_threads.back()->Start(); run_threads.push_back(std::make_unique( &tracker_, post_threads[i * 3 + 2]->TakeTaskSource())); 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(ThreadPoolTaskTrackerTest, LoadWillPostAndRunDuringShutdown) { // Inform |task_tracker_| that a BLOCK_SHUTDOWN task will be posted just to // block shutdown. auto block_shutdown_sequence = WillPostTaskAndQueueTaskSource( CreateTask(), {TaskShutdownBehavior::BLOCK_SHUTDOWN}); EXPECT_TRUE(block_shutdown_sequence); // Start shutdown and try to complete it asynchronously. tracker_.StartShutdown(); ExpectAsyncCompleteShutdownBlocks(); // Post and run tasks asynchronously. std::vector> threads; for (size_t i = 0; i < kLoadTestNumIterations; ++i) { threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false, CreateTask())); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, false, CreateTask())); threads.back()->Start(); threads.push_back(std::make_unique( &tracker_, MakeRefCounted( TaskTraits{TaskShutdownBehavior::BLOCK_SHUTDOWN}, nullptr, TaskSourceExecutionMode::kParallel), ThreadPostingAndRunningTask::Action::WILL_POST_AND_RUN, true, CreateTask())); 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|. RunAndPopNextTask(std::move(block_shutdown_sequence)); 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(ThreadPoolTaskTrackerTest, RunAndPopNextTaskReturnsSequenceToReschedule) { TaskTraits default_traits; Task task_1(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); EXPECT_TRUE( tracker_.WillPostTask(&task_1, default_traits.shutdown_behavior())); Task task_2(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()); EXPECT_TRUE( tracker_.WillPostTask(&task_2, default_traits.shutdown_behavior())); scoped_refptr sequence = test::CreateSequenceWithTask(std::move(task_1), default_traits); { auto transaction = sequence->BeginTransaction(); transaction.WillPushImmediateTask(); transaction.PushImmediateTask(std::move(task_2)); } EXPECT_EQ(sequence, test::QueueAndRunTaskSource(&tracker_, sequence).Unregister()); } namespace { class WaitAllowedTestThread : public SimpleThread { public: WaitAllowedTestThread() : SimpleThread("WaitAllowedTestThread") {} WaitAllowedTestThread(const WaitAllowedTestThread&) = delete; WaitAllowedTestThread& operator=(const WaitAllowedTestThread&) = delete; private: void Run() override { auto task_tracker = std::make_unique(); // 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, BindOnce([] { EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); }); }), TimeTicks::Now(), TimeDelta()); TaskTraits default_traits; EXPECT_TRUE(task_tracker->WillPostTask(&task_without_sync_primitives, default_traits.shutdown_behavior())); auto sequence_without_sync_primitives = test::CreateSequenceWithTask( std::move(task_without_sync_primitives), default_traits); test::QueueAndRunTaskSource(task_tracker.get(), std::move(sequence_without_sync_primitives)); // Expect TaskTracker to keep waiting allowed when running a task with the // WithBaseSyncPrimitives() trait. internal::AssertBaseSyncPrimitivesAllowed(); Task task_with_sync_primitives( FROM_HERE, BindOnce([] { // Shouldn't fail. internal::AssertBaseSyncPrimitivesAllowed(); }), TimeTicks::Now(), TimeDelta()); TaskTraits traits_with_sync_primitives = TaskTraits(WithBaseSyncPrimitives()); EXPECT_TRUE(task_tracker->WillPostTask( &task_with_sync_primitives, traits_with_sync_primitives.shutdown_behavior())); auto sequence_with_sync_primitives = test::CreateSequenceWithTask( std::move(task_with_sync_primitives), traits_with_sync_primitives); test::QueueAndRunTaskSource(task_tracker.get(), std::move(sequence_with_sync_primitives)); ScopedAllowBaseSyncPrimitivesForTesting allow_wait_in_task_tracker_destructor; task_tracker.reset(); } }; } // namespace // Verify that AssertIOAllowed() succeeds only for a WithBaseSyncPrimitives() // task. TEST(ThreadPoolTaskTrackerWaitAllowedTest, 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. GTEST_FLAG_SET(death_test_style, "threadsafe"); WaitAllowedTestThread wait_allowed_test_thread; wait_allowed_test_thread.Start(); wait_allowed_test_thread.Join(); } } // namespace internal } // namespace base