| // Copyright 2018 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/sequence_manager/thread_controller_with_message_pump_impl.h" |
| |
| #include "base/bind_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #include "base/debug/stack_trace.h" |
| |
| #include <queue> |
| |
| using testing::_; |
| using testing::Invoke; |
| using testing::ElementsAre; |
| |
| namespace base { |
| namespace sequence_manager { |
| |
| namespace { |
| |
| class ThreadControllerForTest |
| : public internal::ThreadControllerWithMessagePumpImpl { |
| public: |
| ThreadControllerForTest(std::unique_ptr<MessagePump> pump, |
| const TickClock* clock) |
| : ThreadControllerWithMessagePumpImpl(std::move(pump), clock) {} |
| |
| using ThreadControllerWithMessagePumpImpl::DoDelayedWork; |
| using ThreadControllerWithMessagePumpImpl::DoIdleWork; |
| using ThreadControllerWithMessagePumpImpl::DoWork; |
| using ThreadControllerWithMessagePumpImpl::EnsureWorkScheduled; |
| using ThreadControllerWithMessagePumpImpl::Quit; |
| }; |
| |
| class MockMessagePump : public MessagePump { |
| public: |
| MockMessagePump() {} |
| ~MockMessagePump() override {} |
| |
| MOCK_METHOD1(Run, void(MessagePump::Delegate*)); |
| MOCK_METHOD0(Quit, void()); |
| MOCK_METHOD0(ScheduleWork, void()); |
| MOCK_METHOD1(ScheduleDelayedWork, void(const TimeTicks&)); |
| MOCK_METHOD1(SetTimerSlack, void(TimerSlack)); |
| }; |
| |
| // TODO(crbug.com/901373): Deduplicate FakeTaskRunners. |
| class FakeTaskRunner : public SingleThreadTaskRunner { |
| public: |
| bool PostDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| return true; |
| } |
| |
| bool PostNonNestableDelayedTask(const Location& from_here, |
| OnceClosure task, |
| TimeDelta delay) override { |
| return true; |
| } |
| |
| bool RunsTasksInCurrentSequence() const override { return true; } |
| |
| protected: |
| ~FakeTaskRunner() override = default; |
| }; |
| |
| class FakeSequencedTaskSource : public internal::SequencedTaskSource { |
| public: |
| explicit FakeSequencedTaskSource(TickClock* clock) : clock_(clock) {} |
| ~FakeSequencedTaskSource() override = default; |
| |
| Optional<PendingTask> TakeTask() override { |
| if (tasks_.empty()) |
| return nullopt; |
| if (tasks_.front().delayed_run_time > clock_->NowTicks()) |
| return nullopt; |
| PendingTask task = std::move(tasks_.front()); |
| tasks_.pop(); |
| return task; |
| } |
| |
| void DidRunTask() override {} |
| |
| TimeDelta DelayTillNextTask(LazyNow* lazy_now) const override { |
| if (tasks_.empty()) |
| return TimeDelta::Max(); |
| if (tasks_.front().delayed_run_time.is_null()) |
| return TimeDelta(); |
| if (lazy_now->Now() > tasks_.front().delayed_run_time) |
| return TimeDelta(); |
| return tasks_.front().delayed_run_time - lazy_now->Now(); |
| } |
| |
| void AddTask(PendingTask task) { |
| DCHECK(tasks_.empty() || task.delayed_run_time.is_null() || |
| tasks_.back().delayed_run_time < task.delayed_run_time); |
| tasks_.push(std::move(task)); |
| } |
| |
| bool HasPendingHighResolutionTasks() override { return false; } |
| |
| bool OnSystemIdle() override { return false; } |
| |
| private: |
| TickClock* clock_; |
| std::queue<PendingTask> tasks_; |
| }; |
| |
| TimeTicks Seconds(int seconds) { |
| return TimeTicks() + TimeDelta::FromSeconds(seconds); |
| } |
| |
| TimeTicks Days(int seconds) { |
| return TimeTicks() + TimeDelta::FromDays(seconds); |
| } |
| |
| } // namespace |
| |
| class ThreadControllerWithMessagePumpTest : public testing::Test { |
| public: |
| ThreadControllerWithMessagePumpTest() |
| : message_pump_(new testing::StrictMock<MockMessagePump>()), |
| thread_controller_(std::unique_ptr<MessagePump>(message_pump_), |
| &clock_), |
| task_source_(&clock_) { |
| thread_controller_.SetWorkBatchSize(1); |
| thread_controller_.SetSequencedTaskSource(&task_source_); |
| } |
| |
| protected: |
| MockMessagePump* message_pump_; |
| SimpleTestTickClock clock_; |
| ThreadControllerForTest thread_controller_; |
| FakeSequencedTaskSource task_source_; |
| }; |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, ScheduleDelayedWork) { |
| TimeTicks next_run_time; |
| |
| MockCallback<OnceClosure> task1; |
| task_source_.AddTask(PendingTask(FROM_HERE, task1.Get(), Seconds(10))); |
| MockCallback<OnceClosure> task2; |
| task_source_.AddTask(PendingTask(FROM_HERE, task2.Get(), TimeTicks())); |
| MockCallback<OnceClosure> task3; |
| task_source_.AddTask(PendingTask(FROM_HERE, task3.Get(), Seconds(20))); |
| |
| // Call a no-op DoWork. Expect that it doesn't do any work, but |
| // schedules a delayed wake-up appropriately. |
| clock_.SetNowTicks(Seconds(5)); |
| EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(10))); |
| EXPECT_FALSE(thread_controller_.DoWork()); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| |
| // Call DoDelayedWork after the expiration of the delay. |
| // Expect that a task will run and the next delay will equal to |
| // TimeTicks() as we have immediate work to do. |
| clock_.SetNowTicks(Seconds(11)); |
| EXPECT_CALL(task1, Run()).Times(1); |
| EXPECT_TRUE(thread_controller_.DoDelayedWork(&next_run_time)); |
| EXPECT_EQ(next_run_time, TimeTicks()); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| testing::Mock::VerifyAndClearExpectations(&task1); |
| |
| // Call DoWork immediately after the previous call. Expect a new task |
| // to be run. |
| EXPECT_CALL(task2, Run()).Times(1); |
| EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(20))); |
| EXPECT_TRUE(thread_controller_.DoWork()); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| testing::Mock::VerifyAndClearExpectations(&task2); |
| |
| // Call DoDelayedWork for the last task and expect to be told |
| // about the lack of further delayed work (next run time being TimeTicks()). |
| clock_.SetNowTicks(Seconds(21)); |
| EXPECT_CALL(task3, Run()).Times(1); |
| EXPECT_TRUE(thread_controller_.DoDelayedWork(&next_run_time)); |
| EXPECT_EQ(next_run_time, TimeTicks()); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| testing::Mock::VerifyAndClearExpectations(&task3); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, SetNextDelayedDoWork) { |
| EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Seconds(123))); |
| |
| LazyNow lazy_now(&clock_); |
| thread_controller_.SetNextDelayedDoWork(&lazy_now, Seconds(123)); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, SetNextDelayedDoWork_CapAtOneDay) { |
| EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Days(1))); |
| |
| LazyNow lazy_now(&clock_); |
| thread_controller_.SetNextDelayedDoWork(&lazy_now, Days(2)); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, DelayedWork_CapAtOneDay) { |
| MockCallback<OnceClosure> task1; |
| task_source_.AddTask(PendingTask(FROM_HERE, task1.Get(), Days(10))); |
| |
| EXPECT_CALL(*message_pump_, ScheduleDelayedWork(Days(1))); |
| EXPECT_FALSE(thread_controller_.DoWork()); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, NestedExecution) { |
| // This test posts three immediate tasks. The first creates a nested RunLoop |
| // and the test expects that the second and third tasks are run outside of |
| // the nested loop. |
| std::vector<std::string> log; |
| |
| ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>()); |
| |
| EXPECT_CALL(*message_pump_, Run(_)) |
| .WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) { |
| log.push_back("entering top-level runloop"); |
| EXPECT_EQ(delegate, &thread_controller_); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_FALSE(delegate->DoWork()); |
| log.push_back("exiting top-level runloop"); |
| })) |
| .WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) { |
| log.push_back("entering nested runloop"); |
| EXPECT_EQ(delegate, &thread_controller_); |
| EXPECT_FALSE(thread_controller_.IsTaskExecutionAllowed()); |
| EXPECT_FALSE(delegate->DoWork()); |
| log.push_back("exiting nested runloop"); |
| })); |
| |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task1"); |
| RunLoop().Run(); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task2"); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task3"); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| |
| EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed()); |
| RunLoop().Run(); |
| |
| EXPECT_THAT(log, |
| ElementsAre("entering top-level runloop", "task1", |
| "entering nested runloop", "exiting nested runloop", |
| "task2", "task3", "exiting top-level runloop")); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, |
| NestedExecutionWithApplicationTasks) { |
| // THis test is similar to the previous one, but execution is explicitly |
| // allowed (by specifying appropriate RunLoop type), and tasks are run inside |
| // nested runloop. |
| std::vector<std::string> log; |
| |
| ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>()); |
| |
| EXPECT_CALL(*message_pump_, Run(_)) |
| .WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) { |
| log.push_back("entering top-level runloop"); |
| EXPECT_EQ(delegate, &thread_controller_); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_FALSE(delegate->DoWork()); |
| log.push_back("exiting top-level runloop"); |
| })) |
| .WillOnce(Invoke([&log, this](MessagePump::Delegate* delegate) { |
| log.push_back("entering nested runloop"); |
| EXPECT_EQ(delegate, &thread_controller_); |
| EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed()); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_FALSE(delegate->DoWork()); |
| log.push_back("exiting nested runloop"); |
| })); |
| // An extra schedule work will be called when entering a nested runloop. |
| EXPECT_CALL(*message_pump_, ScheduleWork()); |
| |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task1"); |
| RunLoop(RunLoop::Type::kNestableTasksAllowed).Run(); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task2"); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| task_source_.AddTask( |
| PendingTask(FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log, |
| ThreadControllerForTest* controller) { |
| EXPECT_FALSE(controller->IsTaskExecutionAllowed()); |
| log->push_back("task3"); |
| }, |
| &log, &thread_controller_), |
| TimeTicks())); |
| |
| EXPECT_TRUE(thread_controller_.IsTaskExecutionAllowed()); |
| RunLoop().Run(); |
| |
| EXPECT_THAT( |
| log, ElementsAre("entering top-level runloop", "task1", |
| "entering nested runloop", "task2", "task3", |
| "exiting nested runloop", "exiting top-level runloop")); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, SetDefaultTaskRunner) { |
| scoped_refptr<SingleThreadTaskRunner> task_runner1 = |
| MakeRefCounted<FakeTaskRunner>(); |
| thread_controller_.SetDefaultTaskRunner(task_runner1); |
| EXPECT_EQ(task_runner1, ThreadTaskRunnerHandle::Get()); |
| |
| // Check that we are correctly supporting overriding. |
| scoped_refptr<SingleThreadTaskRunner> task_runner2 = |
| MakeRefCounted<FakeTaskRunner>(); |
| thread_controller_.SetDefaultTaskRunner(task_runner2); |
| EXPECT_EQ(task_runner2, ThreadTaskRunnerHandle::Get()); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, EnsureWorkScheduled) { |
| task_source_.AddTask(PendingTask(FROM_HERE, DoNothing(), TimeTicks())); |
| |
| // Ensure that the first ScheduleWork() call results in the pump being called. |
| EXPECT_CALL(*message_pump_, ScheduleWork()); |
| thread_controller_.ScheduleWork(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| |
| // Ensure that the subsequent ScheduleWork() does not call the pump. |
| thread_controller_.ScheduleWork(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| |
| // Ensure that EnsureWorkScheduled() forces a call to a pump. |
| EXPECT_CALL(*message_pump_, ScheduleWork()); |
| thread_controller_.EnsureWorkScheduled(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, WorkBatching) { |
| ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>()); |
| |
| const int kBatchSize = 5; |
| thread_controller_.SetWorkBatchSize(kBatchSize); |
| |
| int task_count = 0; |
| EXPECT_CALL(*message_pump_, Run(_)) |
| .WillOnce(Invoke([&](MessagePump::Delegate* delegate) { |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_EQ(5, task_count); |
| })); |
| |
| for (int i = 0; i < kBatchSize; i++) { |
| task_source_.AddTask(PendingTask( |
| FROM_HERE, BindLambdaForTesting([&] { task_count++; }), TimeTicks())); |
| } |
| |
| RunLoop run_loop; |
| run_loop.Run(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, QuitInterruptsBatch) { |
| // This check ensures that RunLoop::Quit() makes us drop back to a work batch |
| // size of 1. |
| ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>()); |
| |
| const int kBatchSize = 5; |
| thread_controller_.SetWorkBatchSize(kBatchSize); |
| |
| int task_count = 0; |
| EXPECT_CALL(*message_pump_, Run(_)) |
| .WillOnce(Invoke([&](MessagePump::Delegate* delegate) { |
| TimeTicks next_time; |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_EQ(1, task_count); |
| |
| // Somewhat counter-intuitive, but if the pump keeps calling us after |
| // Quit(), the delegate should still run tasks as normally. This is |
| // needed to support nested OS-level runloops that still pump |
| // application tasks (e.g., showing a popup menu on Mac). |
| EXPECT_TRUE(delegate->DoDelayedWork(&next_time)); |
| EXPECT_EQ(2, task_count); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_EQ(3, task_count); |
| })); |
| EXPECT_CALL(*message_pump_, Quit()); |
| |
| RunLoop run_loop; |
| for (int i = 0; i < kBatchSize; i++) { |
| task_source_.AddTask(PendingTask(FROM_HERE, BindLambdaForTesting([&] { |
| if (!task_count++) |
| run_loop.Quit(); |
| }), |
| TimeTicks())); |
| } |
| |
| run_loop.Run(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| TEST_F(ThreadControllerWithMessagePumpTest, EarlyQuit) { |
| // This test ensures that a opt-of-runloop Quit() (which is possible with some |
| // pump implementations) doesn't affect the next RunLoop::Run call. |
| |
| ThreadTaskRunnerHandle handle(MakeRefCounted<FakeTaskRunner>()); |
| |
| std::vector<std::string> log; |
| |
| // This quit should be a no-op for future calls. |
| EXPECT_CALL(*message_pump_, Quit()); |
| thread_controller_.Quit(); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| |
| EXPECT_CALL(*message_pump_, Run(_)) |
| .WillOnce(Invoke([this](MessagePump::Delegate* delegate) { |
| EXPECT_EQ(delegate, &thread_controller_); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_TRUE(delegate->DoWork()); |
| EXPECT_FALSE(delegate->DoWork()); |
| })); |
| |
| RunLoop run_loop; |
| |
| task_source_.AddTask(PendingTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log) { log->push_back("task1"); }, &log), |
| TimeTicks())); |
| task_source_.AddTask(PendingTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](std::vector<std::string>* log) { log->push_back("task2"); }, &log), |
| TimeTicks())); |
| |
| run_loop.RunUntilIdle(); |
| |
| EXPECT_THAT(log, ElementsAre("task1", "task2")); |
| testing::Mock::VerifyAndClearExpectations(message_pump_); |
| } |
| |
| } // namespace sequence_manager |
| } // namespace base |