/*
* 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 "repeating_timer.h"
#include
#include
#include "message_loop_thread.h"
namespace bluetooth {
namespace common {
constexpr std::chrono::microseconds kMinimumPeriod =
std::chrono::microseconds(1);
// This runs on user thread
RepeatingTimer::~RepeatingTimer() {
std::lock_guard api_lock(api_mutex_);
if (message_loop_thread_ != nullptr && message_loop_thread_->IsRunning()) {
CancelAndWait();
}
}
// This runs on user thread
bool RepeatingTimer::SchedulePeriodic(
const base::WeakPtr& thread,
const base::Location& from_here, base::RepeatingClosure task,
std::chrono::microseconds period) {
if (period < kMinimumPeriod) {
log::error("period must be at least {}", kMinimumPeriod.count());
return false;
}
uint64_t time_now_us = clock_tick_us_();
uint64_t time_next_task_us = time_now_us + period.count();
std::lock_guard api_lock(api_mutex_);
if (thread == nullptr) {
log::error("thread must be non-null");
return false;
}
CancelAndWait();
expected_time_next_task_us_ = time_next_task_us;
task_ = std::move(task);
task_wrapper_.Reset(
base::Bind(&RepeatingTimer::RunTask, base::Unretained(this)));
message_loop_thread_ = thread;
period_ = period;
uint64_t time_until_next_us = time_next_task_us - clock_tick_us_();
if (!thread->DoInThreadDelayed(
from_here, task_wrapper_.callback(),
std::chrono::microseconds(time_until_next_us))) {
log::error("failed to post task to message loop for thread {}, from {}",
*thread, from_here.ToString());
expected_time_next_task_us_ = 0;
task_wrapper_.Cancel();
message_loop_thread_ = nullptr;
period_ = {};
return false;
}
return true;
}
// This runs on user thread
void RepeatingTimer::Cancel() {
std::promise promise;
CancelHelper(std::move(promise));
}
// This runs on user thread
void RepeatingTimer::CancelAndWait() {
std::promise promise;
auto future = promise.get_future();
CancelHelper(std::move(promise));
future.wait();
}
// This runs on user thread
void RepeatingTimer::CancelHelper(std::promise promise) {
std::lock_guard api_lock(api_mutex_);
MessageLoopThread* scheduled_thread = message_loop_thread_.get();
if (scheduled_thread == nullptr) {
promise.set_value();
return;
}
if (scheduled_thread->GetThreadId() == base::PlatformThread::CurrentId()) {
CancelClosure(std::move(promise));
return;
}
scheduled_thread->DoInThread(
FROM_HERE, base::BindOnce(&RepeatingTimer::CancelClosure,
base::Unretained(this), std::move(promise)));
}
// This runs on message loop thread
void RepeatingTimer::CancelClosure(std::promise promise) {
message_loop_thread_ = nullptr;
task_wrapper_.Cancel();
#if BASE_VER < 927031
task_ = {};
#else
task_ = base::NullCallback();
#endif
period_ = std::chrono::microseconds(0);
expected_time_next_task_us_ = 0;
promise.set_value();
}
// This runs on user thread
bool RepeatingTimer::IsScheduled() const {
std::lock_guard api_lock(api_mutex_);
return message_loop_thread_ != nullptr && message_loop_thread_->IsRunning();
}
// This runs on message loop thread
void RepeatingTimer::RunTask() {
if (message_loop_thread_ == nullptr || !message_loop_thread_->IsRunning()) {
log::error("message_loop_thread_ is null or is not running");
return;
}
log::assert_that(
message_loop_thread_->GetThreadId() == base::PlatformThread::CurrentId(),
"task must run on message loop thread");
int64_t period_us = period_.count();
expected_time_next_task_us_ += period_us;
uint64_t time_now_us = clock_tick_us_();
int64_t remaining_time_us = expected_time_next_task_us_ - time_now_us;
if (remaining_time_us < 0) {
// if remaining_time_us is negative, schedule the task to the nearest
// multiple of period
remaining_time_us = (remaining_time_us % period_us + period_us) % period_us;
}
message_loop_thread_->DoInThreadDelayed(
FROM_HERE, task_wrapper_.callback(),
std::chrono::microseconds(remaining_time_us));
uint64_t time_before_task_us = clock_tick_us_();
task_.Run();
uint64_t time_after_task_us = clock_tick_us_();
auto task_time_us =
static_cast(time_after_task_us - time_before_task_us);
if (task_time_us > period_.count()) {
log::error(
"Periodic task execution took {} microseconds, longer than interval {} "
"microseconds",
task_time_us, period_.count());
}
}
} // namespace common
} // namespace bluetooth