blob: c6d3687553b91fecacdc901529f5e73ac881b33d [file] [log] [blame]
/*
* Copyright (C) 2021 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 "RecurrentTimer.h"
#include <utils/Log.h>
#include <utils/SystemClock.h>
#include <inttypes.h>
#include <math.h>
namespace android {
namespace hardware {
namespace automotive {
namespace vehicle {
using ::android::base::ScopedLockAssertion;
RecurrentTimer::RecurrentTimer() {
mThread = std::thread(&RecurrentTimer::loop, this);
}
RecurrentTimer::~RecurrentTimer() {
{
std::scoped_lock<std::mutex> lockGuard(mLock);
mStopRequested = true;
}
mCond.notify_one();
if (mThread.joinable()) {
mThread.join();
}
}
void RecurrentTimer::registerTimerCallback(int64_t intervalInNano,
std::shared_ptr<RecurrentTimer::Callback> callback) {
{
std::scoped_lock<std::mutex> lockGuard(mLock);
// Aligns the nextTime to multiply of interval.
int64_t nextTime = ceil(uptimeNanos() / intervalInNano) * intervalInNano;
std::unique_ptr<CallbackInfo> info = std::make_unique<CallbackInfo>();
info->callback = callback;
info->interval = intervalInNano;
info->nextTime = nextTime;
auto it = mCallbacks.find(callback);
if (it != mCallbacks.end()) {
ALOGI("Replacing an existing timer callback with a new interval, current: %" PRId64
" ns, new: %" PRId64 " ns",
it->second->interval, intervalInNano);
markOutdatedLocked(it->second);
}
mCallbacks[callback] = info.get();
mCallbackQueue.push_back(std::move(info));
// Insert the last element into the heap.
std::push_heap(mCallbackQueue.begin(), mCallbackQueue.end(), CallbackInfo::cmp);
}
mCond.notify_one();
}
void RecurrentTimer::unregisterTimerCallback(std::shared_ptr<RecurrentTimer::Callback> callback) {
{
std::scoped_lock<std::mutex> lockGuard(mLock);
auto it = mCallbacks.find(callback);
if (it == mCallbacks.end()) {
ALOGE("No event found to unregister");
return;
}
markOutdatedLocked(it->second);
mCallbacks.erase(it);
}
mCond.notify_one();
}
void RecurrentTimer::markOutdatedLocked(RecurrentTimer::CallbackInfo* info) {
info->outdated = true;
info->callback = nullptr;
// Make sure the first element is always valid.
removeInvalidCallbackLocked();
}
void RecurrentTimer::removeInvalidCallbackLocked() {
while (mCallbackQueue.size() != 0 && mCallbackQueue[0]->outdated) {
std::pop_heap(mCallbackQueue.begin(), mCallbackQueue.end(), CallbackInfo::cmp);
mCallbackQueue.pop_back();
}
}
std::shared_ptr<RecurrentTimer::Callback> RecurrentTimer::getNextCallbackLocked(int64_t now) {
std::pop_heap(mCallbackQueue.begin(), mCallbackQueue.end(), CallbackInfo::cmp);
auto& callbackInfo = mCallbackQueue[mCallbackQueue.size() - 1];
auto nextCallback = callbackInfo->callback;
// intervalCount is the number of interval we have to advance until we pass now.
size_t intervalCount = (now - callbackInfo->nextTime) / callbackInfo->interval + 1;
callbackInfo->nextTime += intervalCount * callbackInfo->interval;
std::push_heap(mCallbackQueue.begin(), mCallbackQueue.end(), CallbackInfo::cmp);
// Make sure the first element is always valid.
removeInvalidCallbackLocked();
return nextCallback;
}
void RecurrentTimer::loop() {
std::vector<std::shared_ptr<Callback>> callbacksToRun;
while (true) {
{
std::unique_lock<std::mutex> uniqueLock(mLock);
ScopedLockAssertion lockAssertion(mLock);
// Wait until the timer exits or we have at least one recurrent callback.
mCond.wait(uniqueLock, [this] {
ScopedLockAssertion lockAssertion(mLock);
return mStopRequested || mCallbackQueue.size() != 0;
});
int64_t interval;
if (mStopRequested) {
return;
}
// The first element is the nearest next event.
int64_t nextTime = mCallbackQueue[0]->nextTime;
int64_t now = uptimeNanos();
if (nextTime > now) {
interval = nextTime - now;
} else {
interval = 0;
}
// Wait for the next event or the timer exits.
if (mCond.wait_for(uniqueLock, std::chrono::nanoseconds(interval), [this] {
ScopedLockAssertion lockAssertion(mLock);
return mStopRequested;
})) {
return;
}
now = uptimeNanos();
callbacksToRun.clear();
while (mCallbackQueue.size() > 0) {
int64_t nextTime = mCallbackQueue[0]->nextTime;
if (nextTime > now) {
break;
}
callbacksToRun.push_back(getNextCallbackLocked(now));
}
}
// Do not execute the callback while holding the lock.
for (size_t i = 0; i < callbacksToRun.size(); i++) {
(*callbacksToRun[i])();
}
}
}
bool RecurrentTimer::CallbackInfo::cmp(const std::unique_ptr<RecurrentTimer::CallbackInfo>& lhs,
const std::unique_ptr<RecurrentTimer::CallbackInfo>& rhs) {
return lhs->nextTime > rhs->nextTime;
}
} // namespace vehicle
} // namespace automotive
} // namespace hardware
} // namespace android