Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7d8c660 authored by Hansong Zhang's avatar Hansong Zhang
Browse files

Reactor-based alarm implementation

* Use Reactor+Thread (common/thread.h) to replace existing
  libchrome-based message_loop_thread
* Use Alarm/RepeatingAlarm to replace existing Timer, by using
  kernel-based timerfd, to make all components unified

Bug: 110303473
Test: run unit test, and run benchmark
Change-Id: I6d6bc8dc3897649d0f6cc00ce0aa7054a3ddc09d
parent 8bb04bb8
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -11,11 +11,13 @@ cc_library_static {
    ],
    srcs: [
        "address_obfuscator.cc",
        "alarm.cc",
        "handler.cc",
        "message_loop_thread.cc",
        "metrics.cc",
        "once_timer.cc",
        "reactor.cc",
        "repeating_alarm.cc",
        "repeating_timer.cc",
        "thread.cc",
        "time_util.cc",
@@ -42,12 +44,14 @@ cc_test {
    ],
    srcs : [
        "address_obfuscator_unittest.cc",
        "alarm_unittest.cc",
        "handler_unittest.cc",
        "leaky_bonded_queue_unittest.cc",
        "message_loop_thread_unittest.cc",
        "metrics_unittest.cc",
        "once_timer_unittest.cc",
        "reactor_unittest.cc",
        "repeating_alarm_unittest.cc",
        "repeating_timer_unittest.cc",
        "state_machine_unittest.cc",
        "thread_unittest.cc",
@@ -121,6 +125,7 @@ cc_benchmark {
        "liblog",
        "libcrypto",
        "libprotobuf-cpp-lite",
        "libcrypto",
        "libcutils",
    ],
    static_libs: [

system/common/alarm.cc

0 → 100644
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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 "alarm.h"

#include <sys/timerfd.h>
#include <cstring>

#include "base/logging.h"
#include "utils.h"

namespace bluetooth {
namespace common {

Alarm::Alarm(Thread* thread)
  : thread_(thread),
    fd_(timerfd_create(CLOCK_BOOTTIME_ALARM, 0)) {
  CHECK_NE(fd_, -1) << __func__ << ": cannot create timerfd: " << strerror(errno);

  token_ = thread_->GetReactor()->Register(fd_, [this] { on_fire(); }, nullptr);
}

Alarm::~Alarm() {
  thread_->GetReactor()->Unregister(token_);

  int close_status;
  RUN_NO_INTR(close_status = close(fd_));
  CHECK_NE(close_status, -1) << __func__ << ": cannot close timerfd: " << strerror(errno);
}

void Alarm::Schedule(Closure task, std::chrono::milliseconds delay) {
  std::lock_guard<std::mutex> lock(mutex_);
  long delay_ms = delay.count();
  itimerspec timer_itimerspec{
    {/* interval for periodic timer */},
    {delay_ms / 1000, delay_ms % 1000 * 1000000}
  };
  int result = timerfd_settime(fd_, 0, &timer_itimerspec, nullptr);
  CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);

  task_ = std::move(task);
}

void Alarm::Cancel() {
  std::lock_guard<std::mutex> lock(mutex_);
  itimerspec disarm_itimerspec{/* disarm timer */};
  int result = timerfd_settime(fd_, 0, &disarm_itimerspec, nullptr);
  CHECK_EQ(result, 0) << __func__ << ": failed, error=" << strerror(errno);
}

void Alarm::on_fire() {
  std::unique_lock<std::mutex> lock(mutex_);
  auto task = std::move(task_);
  uint64_t times_invoked;
  auto bytes_read = read(fd_, &times_invoked, sizeof(uint64_t));
  lock.unlock();
  task();
  CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(uint64_t))) << __func__ << ": failed, error=" << strerror(errno);
  CHECK_EQ(times_invoked, static_cast<uint64_t>(1));
}

}  // namespace common
}  // namespace bluetooth

system/common/alarm.h

0 → 100644
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.
 */

#pragma once

#include <functional>
#include <memory>
#include <mutex>

#include "common/thread.h"
#include "common/utils.h"

namespace bluetooth {
namespace common {

// A single-shot alarm for reactor-based thread, implemented by Linux timerfd.
// When it's constructed, it will register a reactable on the specified thread; when it's destroyed, it will unregister
// itself from the thread.
class Alarm {
 public:
  // Create and register a single-shot alarm on given thread
  explicit Alarm(Thread* thread);

  // Unregister this alarm from the thread and release resource
  ~Alarm();

  DISALLOW_COPY_AND_ASSIGN(Alarm);

  // Schedule the alarm with given delay
  void Schedule(Closure task, std::chrono::milliseconds delay);

  // Cancel the alarm. No-op if it's not armed.
  void Cancel();

 private:
  Closure task_;
  Thread* thread_;
  int fd_ = 0;
  Reactor::Reactable* token_;
  mutable std::mutex mutex_;
  void on_fire();
};

}  // namespace common

}  // namespace bluetooth
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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 "alarm.h"

#include <future>

#include "base/logging.h"
#include "gtest/gtest.h"

namespace bluetooth {
namespace common {
namespace {

class AlarmTest : public ::testing::Test {
 protected:
  void SetUp() override {
    thread_ = new Thread("test_thread", Thread::Priority::NORMAL);
    alarm_ = new Alarm(thread_);
  }

  void TearDown() override {
    delete alarm_;
    delete thread_;
  }
  Alarm* alarm_;

 private:
  Thread* thread_;
};

TEST_F(AlarmTest, cancel_while_not_armed) {
  alarm_->Cancel();
}

TEST_F(AlarmTest, schedule) {
  std::promise<void> promise;
  auto future = promise.get_future();
  auto before = std::chrono::steady_clock::now();
  int delay_ms = 10;
  int delay_error_ms = 3;
  alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(delay_ms));
  future.get();
  auto after = std::chrono::steady_clock::now();
  auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
  ASSERT_NEAR(duration_ms.count(), delay_ms, delay_error_ms);
}

TEST_F(AlarmTest, cancel_alarm) {
  alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(3));
  alarm_->Cancel();
  std::this_thread::sleep_for(std::chrono::milliseconds(5));
}

TEST_F(AlarmTest, cancel_alarm_from_callback) {
  alarm_->Schedule([this]() { this->alarm_->Cancel(); }, std::chrono::milliseconds(1));
  std::this_thread::sleep_for(std::chrono::milliseconds(5));
}

TEST_F(AlarmTest, schedule_while_alarm_armed) {
  alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(1));
  std::promise<void> promise;
  auto future = promise.get_future();
  alarm_->Schedule([&promise]() { promise.set_value(); }, std::chrono::milliseconds(10));
  future.get();
}

TEST_F(AlarmTest, delete_while_alarm_armed) {
  alarm_->Schedule([]() { ASSERT_TRUE(false) << "Should not happen"; }, std::chrono::milliseconds(1));
  delete alarm_;
  alarm_ = nullptr;
  std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

}  // namespace
}  // namespace common
}  // namespace bluetooth
+88 −0
Original line number Diff line number Diff line
@@ -20,16 +20,22 @@
#include <benchmark/benchmark.h>
#include <future>

#include "common/alarm.h"
#include "common/message_loop_thread.h"
#include "common/once_timer.h"
#include "common/repeating_alarm.h"
#include "common/repeating_timer.h"
#include "common/thread.h"
#include "common/time_util.h"
#include "osi/include/alarm.h"

using ::benchmark::State;
using bluetooth::common::Alarm;
using bluetooth::common::MessageLoopThread;
using bluetooth::common::OnceTimer;
using bluetooth::common::RepeatingAlarm;
using bluetooth::common::RepeatingTimer;
using bluetooth::common::Thread;
using bluetooth::common::time_get_os_boottime_us;

// fake get_main_message_loop implementation for alarm
@@ -268,6 +274,88 @@ BENCHMARK_REGISTER_F(BM_AlarmTaskPeriodicTimer, periodic_accuracy)
    ->Iterations(1)
    ->UseRealTime();

class BM_ReactableAlarm : public ::benchmark::Fixture {
 protected:
  void SetUp(State& st) override {
    ::benchmark::Fixture::SetUp(st);
    thread_ = std::make_unique<Thread>("timer_benchmark", Thread::Priority::REAL_TIME);
    alarm_ = std::make_unique<Alarm>(thread_.get());
    repeating_alarm_ = std::make_unique<RepeatingAlarm>(thread_.get());
    g_map.clear();
    g_promise = std::make_shared<std::promise<void>>();
    g_scheduled_tasks = 0;
    g_task_length = 0;
    g_task_interval = 0;
    g_task_counter = 0;
  }

  void TearDown(State& st) override {
    g_promise = nullptr;
    alarm_ = nullptr;
    repeating_alarm_ = nullptr;
    thread_->Stop();
    thread_ = nullptr;
    ::benchmark::Fixture::TearDown(st);
  }

  std::unique_ptr<Thread> thread_;
  std::unique_ptr<Alarm> alarm_;
  std::unique_ptr<RepeatingAlarm> repeating_alarm_;
};

BENCHMARK_DEFINE_F(BM_ReactableAlarm, timer_performance_ms)(State& state) {
  auto milliseconds = static_cast<int>(state.range(0));
  for (auto _ : state) {
    auto start_time_point = time_get_os_boottime_us();
    alarm_->Schedule(std::bind(TimerFire, nullptr), std::chrono::milliseconds(milliseconds));
    g_promise->get_future().get();
    auto end_time_point = time_get_os_boottime_us();
    auto duration = end_time_point - start_time_point;
    state.SetIterationTime(duration * 1e-6);
    alarm_->Cancel();
  }
};

BENCHMARK_REGISTER_F(BM_ReactableAlarm, timer_performance_ms)
    ->Arg(1)
    ->Arg(5)
    ->Arg(10)
    ->Arg(20)
    ->Arg(100)
    ->Arg(1000)
    ->Arg(2000)
    ->Iterations(1)
    ->UseRealTime();

BENCHMARK_DEFINE_F(BM_ReactableAlarm, periodic_accuracy)
(State& state) {
  for (auto _ : state) {
    g_scheduled_tasks = state.range(0);
    g_task_length = state.range(1);
    g_task_interval = state.range(2);
    g_start_time = time_get_os_boottime_us();
    repeating_alarm_->Schedule([] { AlarmSleepAndCountDelayedTime(nullptr); },
                               std::chrono::milliseconds(g_task_interval));
    g_promise->get_future().get();
    repeating_alarm_->Cancel();
  }
  for (const auto& delay : g_map) {
    state.counters[std::to_string(delay.first)] = delay.second;
  }
};

BENCHMARK_REGISTER_F(BM_ReactableAlarm, periodic_accuracy)
    ->Args({2000, 1, 5})
    ->Args({2000, 3, 5})
    ->Args({2000, 1, 7})
    ->Args({2000, 3, 7})
    ->Args({2000, 1, 20})
    ->Args({2000, 5, 20})
    ->Args({2000, 10, 20})
    ->Args({2000, 15, 20})
    ->Iterations(1)
    ->UseRealTime();

int main(int argc, char** argv) {
  // Disable LOG() output from libchrome
  logging::LoggingSettings log_settings;
Loading