// Copyright 2019 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 "platform/impl/task_runner.h" #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "platform/api/time.h" #include "platform/test/fake_clock.h" #include "util/chrono_helpers.h" namespace openscreen { namespace { using ::testing::_; const auto kTaskRunnerSleepTime = milliseconds(1); constexpr Clock::duration kWaitTimeout = milliseconds(1000); void WaitUntilCondition(std::function predicate) { while (!predicate()) { std::this_thread::sleep_for(kTaskRunnerSleepTime); } } class FakeTaskWaiter final : public TaskRunnerImpl::TaskWaiter { public: explicit FakeTaskWaiter(ClockNowFunctionPtr now_function) : now_function_(now_function) {} ~FakeTaskWaiter() override = default; Error WaitForTaskToBePosted(Clock::duration timeout) override { Clock::time_point start = now_function_(); waiting_.store(true); while (!has_event_.load() && (now_function_() - start) < timeout) { EXPECT_EQ(usleep(100 /* microseconds */), 0); } waiting_.store(false); has_event_.store(false); return Error::None(); } void OnTaskPosted() override { has_event_.store(true); } void WakeUpAndStop() { OnTaskPosted(); task_runner_->RequestStopSoon(); } bool IsWaiting() const { return waiting_.load(); } void SetTaskRunner(TaskRunnerImpl* task_runner) { task_runner_ = task_runner; } private: const ClockNowFunctionPtr now_function_; TaskRunnerImpl* task_runner_; std::atomic has_event_{false}; std::atomic waiting_{false}; }; class TaskRunnerWithWaiterFactory { public: static std::unique_ptr Create( ClockNowFunctionPtr now_function) { fake_waiter = std::make_unique(now_function); auto runner = std::make_unique( now_function, fake_waiter.get(), std::chrono::hours(1)); fake_waiter->SetTaskRunner(runner.get()); return runner; } static std::unique_ptr fake_waiter; }; // static std::unique_ptr TaskRunnerWithWaiterFactory::fake_waiter; } // anonymous namespace TEST(TaskRunnerImplTest, TaskRunnerExecutesTaskAndStops) { FakeClock fake_clock{Clock::time_point(milliseconds(1337))}; TaskRunnerImpl runner(&fake_clock.now); std::string ran_tasks = ""; runner.PostTask([&ran_tasks] { ran_tasks += "1"; }); runner.RequestStopSoon(); runner.RunUntilStopped(); EXPECT_EQ(ran_tasks, "1"); } TEST(TaskRunnerImplTest, TaskRunnerRunsDelayedTasksInOrder) { FakeClock fake_clock{Clock::time_point(milliseconds(1337))}; TaskRunnerImpl runner(&fake_clock.now); std::thread t([&runner] { runner.RunUntilStopped(); }); std::string ran_tasks = ""; const auto kDelayTime = milliseconds(5); const auto task_one = [&ran_tasks] { ran_tasks += "1"; }; runner.PostTaskWithDelay(task_one, kDelayTime); const auto task_two = [&ran_tasks] { ran_tasks += "2"; }; runner.PostTaskWithDelay(task_two, kDelayTime * 2); EXPECT_EQ(ran_tasks, ""); fake_clock.Advance(kDelayTime); WaitUntilCondition([&ran_tasks] { return ran_tasks == "1"; }); EXPECT_EQ(ran_tasks, "1"); fake_clock.Advance(kDelayTime); WaitUntilCondition([&ran_tasks] { return ran_tasks == "12"; }); EXPECT_EQ(ran_tasks, "12"); runner.RequestStopSoon(); t.join(); } TEST(TaskRunnerImplTest, SingleThreadedTaskRunnerRunsSequentially) { FakeClock fake_clock{Clock::time_point(milliseconds(1337))}; TaskRunnerImpl runner(&fake_clock.now); std::string ran_tasks; const auto task_one = [&ran_tasks] { ran_tasks += "1"; }; const auto task_two = [&ran_tasks] { ran_tasks += "2"; }; const auto task_three = [&ran_tasks] { ran_tasks += "3"; }; const auto task_four = [&ran_tasks] { ran_tasks += "4"; }; const auto task_five = [&ran_tasks] { ran_tasks += "5"; }; runner.PostTask(task_one); runner.PostTask(task_two); runner.PostTask(task_three); runner.PostTask(task_four); runner.PostTask(task_five); runner.RequestStopSoon(); EXPECT_EQ(ran_tasks, ""); runner.RunUntilStopped(); EXPECT_EQ(ran_tasks, "12345"); } TEST(TaskRunnerImplTest, RunsAllImmediateTasksBeforeStopping) { FakeClock fake_clock{Clock::time_point(milliseconds(1337))}; TaskRunnerImpl runner(&fake_clock.now); std::string result; runner.PostTask([&] { result += "Alice"; // Post a task that runs just before the quit task. runner.PostTask([&] { result += " says goodbye"; // These tasks will enter the queue after the quit task *and* after the // main loop breaks. They will be executed by the flushing phase. runner.PostTask([&] { result += " and is not"; runner.PostTask([&] { result += " forgotten."; }); }); }); // Post the quit task. runner.RequestStopSoon(); }); EXPECT_EQ(result, ""); runner.RunUntilStopped(); // All posted tasks will execute because RequestStopSoon() guarantees all // immediately-runnable tasks will run before exiting, even if new // immediately-runnable tasks are posted in the meantime. EXPECT_EQ(result, "Alice says goodbye and is not forgotten."); } TEST(TaskRunnerImplTest, TaskRunnerIsStableWithLotsOfTasks) { FakeClock fake_clock{Clock::time_point(milliseconds(1337))}; TaskRunnerImpl runner(&fake_clock.now); const int kNumberOfTasks = 500; std::string expected_ran_tasks; expected_ran_tasks.append(kNumberOfTasks, '1'); std::string ran_tasks; for (int i = 0; i < kNumberOfTasks; ++i) { const auto task = [&ran_tasks] { ran_tasks += "1"; }; runner.PostTask(task); } runner.RequestStopSoon(); runner.RunUntilStopped(); EXPECT_EQ(ran_tasks, expected_ran_tasks); } TEST(TaskRunnerImplTest, TaskRunnerDelayedTasksDontBlockImmediateTasks) { TaskRunnerImpl runner(Clock::now); std::string ran_tasks; const auto task = [&ran_tasks] { ran_tasks += "1"; }; const auto delayed_task = [&ran_tasks] { ran_tasks += "A"; }; runner.PostTaskWithDelay(delayed_task, milliseconds(10000)); runner.PostTask(task); runner.RequestStopSoon(); runner.RunUntilStopped(); // The immediate task should have run, even though the delayed task // was added first. EXPECT_EQ(ran_tasks, "1"); } TEST(TaskRunnerImplTest, TaskRunnerUsesEventWaiter) { std::unique_ptr runner = TaskRunnerWithWaiterFactory::Create(Clock::now); std::atomic x{0}; std::thread t([&runner, &x] { runner.get()->RunUntilStopped(); x = 1; }); const Clock::time_point start1 = Clock::now(); FakeTaskWaiter* fake_waiter = TaskRunnerWithWaiterFactory::fake_waiter.get(); while ((Clock::now() - start1) < kWaitTimeout && !fake_waiter->IsWaiting()) { std::this_thread::sleep_for(kTaskRunnerSleepTime); } ASSERT_TRUE(fake_waiter->IsWaiting()); fake_waiter->WakeUpAndStop(); const Clock::time_point start2 = Clock::now(); while ((Clock::now() - start2) < kWaitTimeout && x == 0) { std::this_thread::sleep_for(kTaskRunnerSleepTime); } ASSERT_EQ(x, 1); ASSERT_FALSE(fake_waiter->IsWaiting()); t.join(); } TEST(TaskRunnerImplTest, WakesEventWaiterOnPostTask) { std::unique_ptr runner = TaskRunnerWithWaiterFactory::Create(Clock::now); std::atomic x{0}; std::thread t([&runner] { runner.get()->RunUntilStopped(); }); const Clock::time_point start1 = Clock::now(); FakeTaskWaiter* fake_waiter = TaskRunnerWithWaiterFactory::fake_waiter.get(); while ((Clock::now() - start1) < kWaitTimeout && !fake_waiter->IsWaiting()) { std::this_thread::sleep_for(kTaskRunnerSleepTime); } ASSERT_TRUE(fake_waiter->IsWaiting()); runner->PostTask([&x]() { x = 1; }); const Clock::time_point start2 = Clock::now(); while ((Clock::now() - start2) < kWaitTimeout && x == 0) { std::this_thread::sleep_for(kTaskRunnerSleepTime); } ASSERT_EQ(x, 1); fake_waiter->WakeUpAndStop(); t.join(); } class RepeatedClass { public: MOCK_METHOD0(Repeat, absl::optional()); absl::optional DoCall() { auto result = Repeat(); execution_count++; return result; } std::atomic execution_count{0}; }; } // namespace openscreen