Files
hardware_interfaces/automotive/remoteaccess/test_grpc_server/impl/src/TestWakeupClientServiceImpl.cpp
Yu Shan a90f9c4a43 Add timeout logic to TestWakeupClientServiceImpl.
Add timeout logic for fake tasks. They will timeout after 20s and
print an error message if not received by the remote access HAL.

Test: Manually run TestWakeupClientServiceImpl and verify the log:
Task for client ID: [ID] timed-out
is printed.
Bug: 246841306

Change-Id: I2173c931da9e0ea40c7b16f9e25a75592fa255c0
2022-10-14 17:53:01 -07:00

236 lines
7.7 KiB
C++

/*
* Copyright (C) 2022 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 "TestWakeupClientServiceImpl.h"
#include <android-base/stringprintf.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/SystemClock.h>
#include <chrono>
#include <thread>
namespace android {
namespace hardware {
namespace automotive {
namespace remoteaccess {
namespace {
using ::android::uptimeMillis;
using ::android::base::ScopedLockAssertion;
using ::android::base::StringPrintf;
using ::grpc::ServerContext;
using ::grpc::ServerWriter;
using ::grpc::Status;
constexpr int kTaskIntervalInMs = 5'000;
constexpr int KTaskTimeoutInMs = 20'000;
} // namespace
GetRemoteTasksResponse FakeTaskGenerator::generateTask() {
int clientId = mCurrentClientId++;
GetRemoteTasksResponse response;
response.set_data(std::string(reinterpret_cast<const char*>(DATA), sizeof(DATA)));
std::string clientIdStr = StringPrintf("%d", clientId);
response.set_clientid(clientIdStr);
return response;
}
TaskTimeoutMessageHandler::TaskTimeoutMessageHandler(TaskQueue* taskQueue)
: mTaskQueue(taskQueue) {}
void TaskTimeoutMessageHandler::handleMessage(const android::Message& message) {
mTaskQueue->handleTaskTimeout();
}
TaskQueue::TaskQueue() {
mTaskTimeoutMessageHandler = android::sp<TaskTimeoutMessageHandler>::make(this);
mLooper = Looper::prepare(/*opts=*/0);
mCheckTaskTimeoutThread = std::thread([this] { checkForTestTimeoutLoop(); });
}
TaskQueue::~TaskQueue() {
{
std::lock_guard<std::mutex> lockGuard(mLock);
mStopped = true;
}
while (true) {
// Remove all pending timeout handlers from queue.
if (!maybePopOne().has_value()) {
break;
}
}
if (mCheckTaskTimeoutThread.joinable()) {
mCheckTaskTimeoutThread.join();
}
}
std::optional<GetRemoteTasksResponse> TaskQueue::maybePopOne() {
std::lock_guard<std::mutex> lockGuard(mLock);
if (mTasks.size() == 0) {
return std::nullopt;
}
TaskInfo response = std::move(mTasks.top());
mTasks.pop();
mLooper->removeMessages(mTaskTimeoutMessageHandler, response.taskId);
return std::move(response.taskData);
}
void TaskQueue::add(const GetRemoteTasksResponse& task) {
std::lock_guard<std::mutex> lockGuard(mLock);
if (mStopped) {
return;
}
int taskId = mTaskIdCounter++;
mTasks.push(TaskInfo{
.taskId = taskId,
.timestampInMs = uptimeMillis(),
.taskData = task,
});
android::Message message(taskId);
mLooper->sendMessageDelayed(KTaskTimeoutInMs * 1000, mTaskTimeoutMessageHandler, message);
mTasksNotEmptyCv.notify_all();
}
void TaskQueue::waitForTask() {
std::unique_lock<std::mutex> lock(mLock);
waitForTaskWithLock(lock);
}
void TaskQueue::waitForTaskWithLock(std::unique_lock<std::mutex>& lock) {
mTasksNotEmptyCv.wait(lock, [this] {
ScopedLockAssertion lockAssertion(mLock);
return mTasks.size() > 0 || mStopped;
});
}
void TaskQueue::stopWait() {
std::lock_guard<std::mutex> lockGuard(mLock);
mStopped = true;
mTasksNotEmptyCv.notify_all();
}
void TaskQueue::checkForTestTimeoutLoop() {
Looper::setForThread(mLooper);
while (true) {
{
std::unique_lock<std::mutex> lock(mLock);
if (mStopped) {
ALOGW("The TestWakeupClientServiceImpl is stopping, "
"exiting checkForTestTimeoutLoop");
return;
}
}
mLooper->pollAll(/*timeoutMillis=*/-1);
}
}
void TaskQueue::handleTaskTimeout() {
// We know which task timed-out from the taskId in the message. However, there is no easy way
// to remove a specific task with the task ID from the priority_queue, so we just check from
// the top of the queue (which have the oldest tasks).
std::lock_guard<std::mutex> lockGuard(mLock);
long now = uptimeMillis();
while (mTasks.size() > 0) {
const TaskInfo& taskInfo = mTasks.top();
if (taskInfo.timestampInMs + KTaskTimeoutInMs > now) {
break;
}
// In real implementation, this should report task failure to remote wakeup server.
ALOGW("Task for client ID: %s timed-out, added at %ld ms, now %ld ms",
taskInfo.taskData.clientid().c_str(), taskInfo.timestampInMs, now);
mTasks.pop();
}
}
TestWakeupClientServiceImpl::TestWakeupClientServiceImpl() {
mThread = std::thread([this] { fakeTaskGenerateLoop(); });
}
TestWakeupClientServiceImpl::~TestWakeupClientServiceImpl() {
{
std::lock_guard<std::mutex> lockGuard(mLock);
mServerStopped = true;
mServerStoppedCv.notify_all();
}
mTaskQueue.stopWait();
if (mThread.joinable()) {
mThread.join();
}
}
void TestWakeupClientServiceImpl::fakeTaskGenerateLoop() {
// In actual implementation, this should communicate with the remote server and receives tasks
// from it. Here we simulate receiving one remote task every {kTaskIntervalInMs}ms.
while (true) {
mTaskQueue.add(mFakeTaskGenerator.generateTask());
ALOGI("Sleeping for %d seconds until next task", kTaskIntervalInMs);
std::unique_lock lk(mLock);
if (mServerStoppedCv.wait_for(lk, std::chrono::milliseconds(kTaskIntervalInMs), [this] {
ScopedLockAssertion lockAssertion(mLock);
return mServerStopped;
})) {
// If the stopped flag is set, we are quitting, exit the loop.
return;
}
}
}
Status TestWakeupClientServiceImpl::GetRemoteTasks(ServerContext* context,
const GetRemoteTasksRequest* request,
ServerWriter<GetRemoteTasksResponse>* writer) {
ALOGD("GetRemoteTasks called");
while (true) {
mTaskQueue.waitForTask();
while (true) {
auto maybeTask = mTaskQueue.maybePopOne();
if (!maybeTask.has_value()) {
// No task left, loop again and wait for another task(s).
break;
}
// Loop through all the task in the queue but obtain lock for each element so we don't
// hold lock while writing the response.
const GetRemoteTasksResponse& response = maybeTask.value();
if (!writer->Write(response)) {
// Broken stream, maybe the client is shutting down.
ALOGW("Failed to deliver remote task to remote access HAL");
// The task failed to be sent, add it back to the queue. The order might change, but
// it is okay.
mTaskQueue.add(response);
return Status::CANCELLED;
}
}
}
return Status::OK;
}
Status TestWakeupClientServiceImpl::NotifyWakeupRequired(ServerContext* context,
const NotifyWakeupRequiredRequest* request,
NotifyWakeupRequiredResponse* response) {
return Status::OK;
}
} // namespace remoteaccess
} // namespace automotive
} // namespace hardware
} // namespace android