| // Copyright 2019 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/sequence_manager/task_queue.h" |
| |
| #include "base/message_loop/message_pump.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/task/sequence_manager/sequence_manager.h" |
| #include "base/task/sequence_manager/test/sequence_manager_for_test.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_features.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace sequence_manager { |
| namespace internal { |
| // To avoid symbol collisions in jumbo builds. |
| namespace task_queue_unittest { |
| namespace { |
| |
| TEST(TaskQueueTest, TaskQueueVoters) { |
| auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( |
| MessagePump::Create(MessagePumpType::DEFAULT)); |
| |
| auto queue = |
| sequence_manager->CreateTaskQueue(TaskQueue::Spec(QueueName::TEST_TQ)); |
| |
| // The task queue should be initially enabled. |
| EXPECT_TRUE(queue->IsQueueEnabled()); |
| |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter1 = |
| queue->CreateQueueEnabledVoter(); |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter2 = |
| queue->CreateQueueEnabledVoter(); |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter3 = |
| queue->CreateQueueEnabledVoter(); |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter4 = |
| queue->CreateQueueEnabledVoter(); |
| |
| // Voters should initially vote for the queue to be enabled. |
| EXPECT_TRUE(queue->IsQueueEnabled()); |
| |
| // If any voter wants to disable, the queue is disabled. |
| voter1->SetVoteToEnable(false); |
| EXPECT_FALSE(queue->IsQueueEnabled()); |
| |
| // If the voter is deleted then the queue should be re-enabled. |
| voter1.reset(); |
| EXPECT_TRUE(queue->IsQueueEnabled()); |
| |
| // If any of the remaining voters wants to disable, the queue should be |
| // disabled. |
| voter2->SetVoteToEnable(false); |
| EXPECT_FALSE(queue->IsQueueEnabled()); |
| |
| // If another queue votes to disable, nothing happens because it's already |
| // disabled. |
| voter3->SetVoteToEnable(false); |
| EXPECT_FALSE(queue->IsQueueEnabled()); |
| |
| // There are two votes to disable, so one of them voting to enable does |
| // nothing. |
| voter2->SetVoteToEnable(true); |
| EXPECT_FALSE(queue->IsQueueEnabled()); |
| |
| // IF all queues vote to enable then the queue is enabled. |
| voter3->SetVoteToEnable(true); |
| EXPECT_TRUE(queue->IsQueueEnabled()); |
| } |
| |
| TEST(TaskQueueTest, ShutdownQueueBeforeEnabledVoterDeleted) { |
| auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( |
| MessagePump::Create(MessagePumpType::DEFAULT)); |
| auto queue = |
| sequence_manager->CreateTaskQueue(TaskQueue::Spec(QueueName::TEST_TQ)); |
| |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter = |
| queue->CreateQueueEnabledVoter(); |
| |
| voter->SetVoteToEnable(true); // NOP |
| queue->ShutdownTaskQueue(); |
| |
| // This should complete without DCHECKing. |
| voter.reset(); |
| } |
| |
| TEST(TaskQueueTest, ShutdownQueueBeforeDisabledVoterDeleted) { |
| auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( |
| MessagePump::Create(MessagePumpType::DEFAULT)); |
| auto queue = |
| sequence_manager->CreateTaskQueue(TaskQueue::Spec(QueueName::TEST_TQ)); |
| |
| std::unique_ptr<TaskQueue::QueueEnabledVoter> voter = |
| queue->CreateQueueEnabledVoter(); |
| |
| voter->SetVoteToEnable(false); |
| queue->ShutdownTaskQueue(); |
| |
| // This should complete without DCHECKing. |
| voter.reset(); |
| } |
| |
| class ScopedNoWakeUpsForCanceledTasks { |
| public: |
| explicit ScopedNoWakeUpsForCanceledTasks(bool feature_enabled) { |
| scoped_feature_list_.InitWithFeatureState(kRemoveCanceledTasksInTaskQueue, |
| feature_enabled); |
| TaskQueueImpl::ApplyRemoveCanceledTasksInTaskQueue(); |
| } |
| |
| ~ScopedNoWakeUpsForCanceledTasks() { |
| TaskQueueImpl::ResetRemoveCanceledTasksInTaskQueueForTesting(); |
| } |
| |
| private: |
| test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST(TaskQueueTest, CanceledTaskRemovedIfFeatureEnabled) { |
| for (bool feature_enabled : {false, true}) { |
| ScopedNoWakeUpsForCanceledTasks scoped_no_wake_ups_for_canceled_tasks( |
| feature_enabled); |
| |
| auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( |
| MessagePump::Create(MessagePumpType::DEFAULT)); |
| auto queue = |
| sequence_manager->CreateTaskQueue(TaskQueue::Spec(QueueName::TEST_TQ)); |
| |
| // Get the default task runner. |
| auto task_runner = queue->task_runner(); |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), 0u); |
| |
| bool task_ran = false; |
| DelayedTaskHandle delayed_task_handle = |
| task_runner->PostCancelableDelayedTask( |
| subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE, |
| BindLambdaForTesting([&task_ran]() { task_ran = true; }), |
| Seconds(20)); |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u); |
| |
| // The task is only removed from the queue if the feature is enabled. |
| delayed_task_handle.CancelTask(); |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), feature_enabled ? 0u : 1u); |
| |
| // In any case, the task never actually ran. |
| EXPECT_FALSE(task_ran); |
| } |
| } |
| |
| // Tests that a task posted through `PostCancelableDelayedTask()`, while the |
| // RemoveCanceledDelayedTasksInTaskQueue feature is enabled, is not considered |
| // canceled once it has reached the |delayed_work_queue| and is therefore |
| // not removed. |
| // |
| // This is a regression test for a bug in `Task::IsCanceled()` |
| // (see https://crbug.com/1288882). Note that this function is only called on |
| // tasks inside the |delayed_work_queue|, and not for tasks in the |
| // |delayed_incoming_queue|. This is because a task posted through |
| // `PostCancelableDelayedTask()` is always valid while it is in the |
| // |delayed_incoming_queue|, since canceling it would remove it from the queue. |
| TEST(TaskQueueTest, ValidCancelableTaskIsNotCanceled) { |
| ScopedNoWakeUpsForCanceledTasks scoped_no_wake_ups_for_canceled_tasks(true); |
| |
| auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( |
| MessagePump::Create(MessagePumpType::DEFAULT)); |
| auto queue = |
| sequence_manager->CreateTaskQueue(TaskQueue::Spec(QueueName::TEST_TQ)); |
| |
| // Get the default task runner. |
| auto task_runner = queue->task_runner(); |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), 0u); |
| |
| // RunLoop requires the SingleThreadTaskRunner::CurrentDefaultHandle to be |
| // set. |
| SingleThreadTaskRunner::CurrentDefaultHandle |
| single_thread_task_runner_current_default_handle(task_runner); |
| RunLoop run_loop; |
| |
| // To reach the |delayed_work_queue|, the task must be posted with a non- |
| // zero delay, which is then moved to the |delayed_work_queue| when it is |
| // ripe. To achieve this, run the RunLoop for exactly the same delay of the |
| // cancelable task. Since real time waiting happens, chose a very small delay. |
| constexpr TimeDelta kTestDelay = Microseconds(1); |
| task_runner->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(), kTestDelay); |
| |
| DelayedTaskHandle delayed_task_handle = |
| task_runner->PostCancelableDelayedTask( |
| subtle::PostDelayedTaskPassKeyForTesting(), FROM_HERE, DoNothing(), |
| kTestDelay); |
| run_loop.Run(); |
| |
| // Now only the cancelable delayed task remains and it is ripe. |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u); |
| |
| // ReclaimMemory doesn't remove the task because it is valid (not canceled). |
| sequence_manager->ReclaimMemory(); |
| EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u); |
| |
| // Clean-up. |
| delayed_task_handle.CancelTask(); |
| } |
| |
| } // namespace |
| } // namespace task_queue_unittest |
| } // namespace internal |
| } // namespace sequence_manager |
| } // namespace base |