summaryrefslogtreecommitdiff
path: root/system/common/timer_unittest.cc
diff options
context:
space:
mode:
authorHansong Zhang <hsz@google.com>2018-08-14 14:29:23 -0700
committerHansong Zhang <hsz@google.com>2018-09-13 13:37:42 -0700
commitf05a8c49a889b5a83841e4bf55dd3cf5a1afa8d0 (patch)
tree5bc7d1343c049f61e0bafec0158a3f336dc2b137 /system/common/timer_unittest.cc
parent76250727b2fa2a5a6364edcb20e4d379c82090a4 (diff)
Add Timer as an alternative to osi alarm
* Add a private method MessageLoopThread.DoInThreadDelayed to post a delayed task in message loop, as an alternative approach to osi alarm clock * Add a unit test for MessageLoopThread to check ShutDown() waits until current task finishes * Add Timer using MessageLoopThread.DoInThreadDelayed * Timer provides similar API as osi alarm, and uses same OS clock (boot timer) as alarm * Add benchmark and unit tests to ensure the performance is comparable to the existing osi alarm Test: Run unit test and benchmark test ./test/run_unit_tests.sh bluetooth_test_common ./test/run_benchmarks.sh bluetooth_benchmark_timer_performance --benchmark_repetitions=10 --benchmark_report_aggregates_only=true Bug: 110303473 Change-Id: I6f2e7ae2f80f9889fc5fe3c8cd6b9b2670938b46
Diffstat (limited to 'system/common/timer_unittest.cc')
-rw-r--r--system/common/timer_unittest.cc337
1 files changed, 337 insertions, 0 deletions
diff --git a/system/common/timer_unittest.cc b/system/common/timer_unittest.cc
new file mode 100644
index 0000000000..c6feea91b5
--- /dev/null
+++ b/system/common/timer_unittest.cc
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <gtest/gtest.h>
+#include <future>
+
+#include "message_loop_thread.h"
+#include "timer.h"
+
+using bluetooth::common::MessageLoopThread;
+using bluetooth::common::Timer;
+
+// Allowed error between the expected and actual delay for DoInThreadDelayed().
+constexpr uint32_t delay_error_ms = 3;
+
+/**
+ * Unit tests to verify Task Scheduler.
+ */
+class TimerTest : public ::testing::Test {
+ public:
+ void ShouldNotHappen() { FAIL() << "Should not happen"; }
+
+ void IncreaseTaskCounter(int scheduled_tasks, std::promise<void>* promise) {
+ counter_++;
+ if (counter_ == scheduled_tasks) {
+ promise->set_value();
+ }
+ }
+
+ void GetName(std::string* name, std::promise<void>* promise) {
+ char my_name[256];
+ pthread_getname_np(pthread_self(), my_name, sizeof(my_name));
+ name->append(my_name);
+ promise->set_value();
+ }
+
+ void SleepAndGetName(std::string* name, std::promise<void>* name_promise,
+ int sleep_ms) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
+ GetName(name, name_promise);
+ }
+
+ void VerifyDelayTimeAndSleep(std::chrono::steady_clock::time_point start_time,
+ int interval_ms, int scheduled_tasks,
+ int task_length_ms,
+ std::promise<void>* promise) {
+ auto end_time = std::chrono::steady_clock::now();
+ auto actual_delay = std::chrono::duration_cast<std::chrono::milliseconds>(
+ end_time - start_time);
+ counter_++;
+ int64_t scheduled_delay_ms = interval_ms * counter_;
+ if (counter_ >= scheduled_tasks) {
+ promise->set_value();
+ }
+ ASSERT_NEAR(scheduled_delay_ms, actual_delay.count(), delay_error_ms);
+ std::this_thread::sleep_for(std::chrono::milliseconds(task_length_ms));
+ }
+
+ void VerifyMultipleDelayedTasks(int scheduled_tasks, int task_length_ms,
+ int interval_between_tasks_ms) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ message_loop_thread.EnableRealTimeScheduling();
+ auto future = promise_->get_future();
+ auto start_time = std::chrono::steady_clock::now();
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::VerifyDelayTimeAndSleep, base::Unretained(this),
+ start_time, interval_between_tasks_ms, scheduled_tasks,
+ task_length_ms, promise_),
+ base::TimeDelta::FromMilliseconds(interval_between_tasks_ms));
+ future.get();
+ timer_->CancelAndWait();
+ }
+
+ void CancelTimerAndWait() { timer_->CancelAndWait(); }
+
+ protected:
+ void SetUp() override {
+ ::testing::Test::SetUp();
+ counter_ = 0;
+ timer_ = new Timer();
+ promise_ = new std::promise<void>();
+ }
+
+ void TearDown() override {
+ if (promise_ != nullptr) {
+ delete promise_;
+ promise_ = nullptr;
+ }
+ if (timer_ != nullptr) {
+ delete timer_;
+ timer_ = nullptr;
+ }
+ }
+
+ int counter_;
+ Timer* timer_;
+ std::promise<void>* promise_;
+};
+
+TEST_F(TimerTest, initial_is_not_scheduled) {
+ ASSERT_FALSE(timer_->IsScheduled());
+}
+
+TEST_F(TimerTest, schedule_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ auto future = promise_->get_future();
+ std::string my_name;
+ uint32_t delay_ms = 5;
+
+ timer_->Schedule(message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::GetName, base::Unretained(this),
+ &my_name, promise_),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ EXPECT_TRUE(timer_->IsScheduled());
+ future.get();
+ ASSERT_EQ(name, my_name);
+ EXPECT_FALSE(timer_->IsScheduled());
+}
+
+TEST_F(TimerTest, cancel_without_scheduling) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+
+ EXPECT_FALSE(timer_->IsScheduled());
+ timer_->CancelAndWait();
+ EXPECT_FALSE(timer_->IsScheduled());
+}
+
+TEST_F(TimerTest, cancel_in_callback_no_deadlock) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t delay_ms = 5;
+
+ timer_->Schedule(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::CancelTimerAndWait, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+}
+
+TEST_F(TimerTest, periodic_run) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ auto future = promise_->get_future();
+ uint32_t delay_ms = 5;
+ int num_tasks = 200;
+
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::IncreaseTaskCounter, base::Unretained(this),
+ num_tasks, promise_),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ future.get();
+ ASSERT_EQ(counter_, num_tasks);
+ timer_->CancelAndWait();
+}
+
+TEST_F(TimerTest, schedule_periodic_task_zero_interval) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t interval_ms = 0;
+
+ ASSERT_FALSE(timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(interval_ms)));
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_error_ms));
+}
+
+// Verify that deleting the timer without cancelling it will cancel the task
+TEST_F(TimerTest, periodic_delete_without_cancel) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t delay_ms = 5;
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ delete timer_;
+ timer_ = nullptr;
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_error_ms));
+}
+
+TEST_F(TimerTest, cancel_single_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t delay_ms = 5;
+ uint32_t time_cancellation_ms = 3;
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ std::this_thread::sleep_for(std::chrono::milliseconds(time_cancellation_ms));
+ timer_->Cancel();
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_error_ms));
+}
+
+TEST_F(TimerTest, cancel_periodic_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t delay_ms = 5;
+ uint32_t time_cancellation_ms = 3;
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ std::this_thread::sleep_for(std::chrono::milliseconds(time_cancellation_ms));
+ timer_->CancelAndWait();
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_error_ms));
+}
+
+// Verify that if a task is being executed, then cancelling it is no-op
+TEST_F(TimerTest, cancel_current_task_no_effect) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ auto future = promise_->get_future();
+ std::string my_name;
+ uint32_t delay_ms = 5;
+
+ timer_->Schedule(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::SleepAndGetName, base::Unretained(this), &my_name,
+ promise_, delay_ms),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ EXPECT_TRUE(timer_->IsScheduled());
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(delay_ms + delay_error_ms));
+ timer_->CancelAndWait();
+ future.get();
+ ASSERT_EQ(name, my_name);
+ EXPECT_FALSE(timer_->IsScheduled());
+}
+
+// Schedule 10 short periodic tasks with interval 1 ms between each; verify the
+// functionality
+TEST_F(TimerTest, schedule_multiple_delayed_tasks) {
+ VerifyMultipleDelayedTasks(10, 0, 1);
+}
+
+// Schedule 10 periodic tasks with interval 2 ms between each and each takes 1
+// ms; verify the functionality
+TEST_F(TimerTest, schedule_multiple_delayed_slow_tasks) {
+ VerifyMultipleDelayedTasks(10, 1, 2);
+}
+
+// Schedule 100 periodic tasks with interval 20 ms between each and each takes
+// 10 ms; verify the functionality
+TEST_F(TimerTest, schedule_multiple_delayed_slow_tasks_stress) {
+ VerifyMultipleDelayedTasks(100, 10, 20);
+}
+
+// Verify that when MessageLoopThread is shutdown, the pending task will be
+// cancelled
+TEST_F(TimerTest, message_loop_thread_down_cancel_pending_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ std::string my_name;
+ uint32_t delay_ms = 5;
+
+ timer_->Schedule(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ EXPECT_TRUE(timer_->IsScheduled());
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms - 3));
+ message_loop_thread.ShutDown();
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
+}
+
+// Verify that when MessageLoopThread is shutdown, the pending periodic task
+// will be cancelled
+TEST_F(TimerTest, message_loop_thread_down_cancel_pending_periodic_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ uint32_t delay_ms = 5;
+
+ timer_->Schedule(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ EXPECT_TRUE(timer_->IsScheduled());
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms - 2));
+ message_loop_thread.ShutDown();
+ timer_->CancelAndWait();
+ EXPECT_FALSE(timer_->IsScheduled());
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
+}
+
+TEST_F(TimerTest, schedule_task_cancel_previous_task) {
+ std::string name = "test_thread";
+ MessageLoopThread message_loop_thread(name);
+ message_loop_thread.StartUp();
+ std::string my_name;
+ auto future = promise_->get_future();
+ uint32_t delay_ms = 5;
+
+ timer_->SchedulePeriodic(
+ message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::ShouldNotHappen, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms - 2));
+ timer_->Schedule(message_loop_thread.GetWeakPtr(), FROM_HERE,
+ base::Bind(&TimerTest::GetName, base::Unretained(this),
+ &my_name, promise_),
+ base::TimeDelta::FromMilliseconds(delay_ms));
+ future.wait();
+ ASSERT_EQ(name, my_name);
+}