/*
* Copyright (C) 2017 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 "perfetto/base/android_task_runner.h"
#include <errno.h>
#include <sys/timerfd.h>
namespace perfetto {
namespace base {
AndroidTaskRunner::AndroidTaskRunner()
: looper_(ALooper_prepare(0 /* require callbacks */)),
delayed_timer_(
timerfd_create(kWallTimeClockSource, TFD_NONBLOCK | TFD_CLOEXEC)) {
ALooper_acquire(looper_);
PERFETTO_CHECK(delayed_timer_);
AddFileDescriptorWatch(immediate_event_.fd(),
std::bind(&AndroidTaskRunner::RunImmediateTask, this));
AddFileDescriptorWatch(delayed_timer_.get(),
std::bind(&AndroidTaskRunner::RunDelayedTask, this));
}
AndroidTaskRunner::~AndroidTaskRunner() {
PERFETTO_DCHECK_THREAD(thread_checker_);
std::lock_guard<std::mutex> lock(lock_);
for (const auto& watch : watch_tasks_) {
// ALooper doesn't guarantee that each watch doesn't run one last time if
// the file descriptor was already signalled. To guard against this point
// the watch to a no-op callback.
ALooper_addFd(
looper_, watch.first, ALOOPER_POLL_CALLBACK,
ALOOPER_EVENT_INPUT | ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP,
[](int, int, void*) -> int { return 0; }, nullptr);
ALooper_removeFd(looper_, watch.first);
}
ALooper_release(looper_);
struct itimerspec time = {};
timerfd_settime(delayed_timer_.get(), TFD_TIMER_ABSTIME, &time, nullptr);
}
void AndroidTaskRunner::Run() {
quit_ = false;
for (;;) {
{
std::lock_guard<std::mutex> lock(lock_);
if (quit_)
break;
}
ALooper_pollOnce(-1 /* timeout */, nullptr, nullptr, nullptr);
}
}
void AndroidTaskRunner::Quit() {
std::lock_guard<std::mutex> lock(lock_);
quit_ = true;
ALooper_wake(looper_);
}
bool AndroidTaskRunner::IsIdleForTesting() {
PERFETTO_DCHECK_THREAD(thread_checker_);
std::lock_guard<std::mutex> lock(lock_);
return immediate_tasks_.empty();
}
void AndroidTaskRunner::RunImmediateTask() {
immediate_event_.Clear();
// If locking overhead becomes an issue, add a separate work queue.
bool has_next;
std::function<void()> immediate_task;
{
std::lock_guard<std::mutex> lock(lock_);
if (immediate_tasks_.empty())
return;
immediate_task = std::move(immediate_tasks_.front());
immediate_tasks_.pop_front();
has_next = !immediate_tasks_.empty();
}
// Do another pass through the event loop even if we have immediate tasks to
// run for fairness.
if (has_next)
ScheduleImmediateWakeUp();
errno = 0;
RunTask(immediate_task);
}
void AndroidTaskRunner::RunDelayedTask() {
uint64_t unused = 0;
if (read(delayed_timer_.get(), &unused, sizeof(unused)) != sizeof(unused) &&
errno != EAGAIN) {
PERFETTO_DPLOG("read");
}
std::function<void()> delayed_task;
TimeMillis next_wake_up{};
{
std::lock_guard<std::mutex> lock(lock_);
if (delayed_tasks_.empty())
return;
auto it = delayed_tasks_.begin();
PERFETTO_DCHECK(GetWallTimeMs() >= it->first);
delayed_task = std::move(it->second);
delayed_tasks_.erase(it);
if (!delayed_tasks_.empty())
next_wake_up = delayed_tasks_.begin()->first;
}
if (next_wake_up.count())
ScheduleDelayedWakeUp(next_wake_up);
errno = 0;
RunTask(delayed_task);
}
void AndroidTaskRunner::ScheduleImmediateWakeUp() {
immediate_event_.Notify();
}
void AndroidTaskRunner::ScheduleDelayedWakeUp(TimeMillis time) {
PERFETTO_DCHECK(time.count());
struct itimerspec wake_up = {};
wake_up.it_value = ToPosixTimespec(time);
if (timerfd_settime(delayed_timer_.get(), TFD_TIMER_ABSTIME, &wake_up,
nullptr) == -1) {
PERFETTO_DPLOG("timerfd_settime");
}
}
void AndroidTaskRunner::PostTask(std::function<void()> task) {
bool was_empty;
{
std::lock_guard<std::mutex> lock(lock_);
was_empty = immediate_tasks_.empty();
immediate_tasks_.push_back(std::move(task));
}
if (was_empty)
ScheduleImmediateWakeUp();
}
void AndroidTaskRunner::PostDelayedTask(std::function<void()> task,
uint32_t delay_ms) {
PERFETTO_DCHECK(delay_ms >= 0);
TimeMillis runtime = GetWallTimeMs() + TimeMillis(delay_ms);
bool is_next = false;
{
std::lock_guard<std::mutex> lock(lock_);
auto it = delayed_tasks_.insert(std::make_pair(runtime, std::move(task)));
if (it == delayed_tasks_.begin())
is_next = true;
}
if (is_next)
ScheduleDelayedWakeUp(runtime);
}
void AndroidTaskRunner::AddFileDescriptorWatch(int fd,
std::function<void()> task) {
PERFETTO_DCHECK(fd >= 0);
{
std::lock_guard<std::mutex> lock(lock_);
PERFETTO_DCHECK(!watch_tasks_.count(fd));
watch_tasks_[fd] = std::move(task);
}
// It's safe for the callback to hang on to |this| as everything is
// unregistered in the destructor.
auto callback = [](int signalled_fd, int events, void* data) -> int {
AndroidTaskRunner* task_runner = reinterpret_cast<AndroidTaskRunner*>(data);
return task_runner->OnFileDescriptorEvent(signalled_fd, events) ? 1 : 0;
};
PERFETTO_CHECK(ALooper_addFd(looper_, fd, ALOOPER_POLL_CALLBACK,
ALOOPER_EVENT_INPUT | ALOOPER_EVENT_ERROR |
ALOOPER_EVENT_HANGUP,
std::move(callback), this) != -1);
}
bool AndroidTaskRunner::OnFileDescriptorEvent(int signalled_fd, int events) {
PERFETTO_DCHECK_THREAD(thread_checker_);
if (!(events & (ALOOPER_EVENT_INPUT | ALOOPER_EVENT_ERROR |
ALOOPER_EVENT_HANGUP | ALOOPER_EVENT_INVALID))) {
return true;
}
std::function<void()> task;
{
std::lock_guard<std::mutex> lock(lock_);
auto it = watch_tasks_.find(signalled_fd);
if (it == watch_tasks_.end())
return false;
task = it->second;
}
errno = 0;
RunTask(task);
return true;
}
void AndroidTaskRunner::RemoveFileDescriptorWatch(int fd) {
PERFETTO_DCHECK(fd >= 0);
{
std::lock_guard<std::mutex> lock(lock_);
PERFETTO_DCHECK(watch_tasks_.count(fd));
watch_tasks_.erase(fd);
}
ALooper_removeFd(looper_, fd);
}
bool AndroidTaskRunner::RunsTasksOnCurrentThread() const {
return looper_ == ALooper_forThread();
}
} // namespace base
} // namespace perfetto