blob: eaeba30006cb45e19fd0821b00a25a133fc67af3 [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 "perfetto/ext/base/periodic_task.h"
#include <limits>
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/task_runner.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
(PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
#include <sys/timerfd.h>
#endif
namespace perfetto {
namespace base {
namespace {
uint32_t GetNextDelayMs(const TimeMillis& now_ms,
const PeriodicTask::Args& args) {
if (args.one_shot)
return args.period_ms;
return args.period_ms -
static_cast<uint32_t>(now_ms.count() % args.period_ms);
}
ScopedPlatformHandle CreateTimerFd(const PeriodicTask::Args& args) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
(PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
ScopedPlatformHandle tfd(
timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK));
uint32_t phase_ms = GetNextDelayMs(GetBootTimeMs(), args);
struct itimerspec its {};
// The "1 +" is to make sure that we never pass a zero it_value in the
// unlikely case of phase_ms being 0. That would cause the timer to be
// considered disarmed by timerfd_settime.
its.it_value.tv_sec = static_cast<time_t>(phase_ms / 1000u);
its.it_value.tv_nsec = 1 + static_cast<long>((phase_ms % 1000u) * 1000000u);
if (args.one_shot) {
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 0;
} else {
const uint32_t period_ms = args.period_ms;
its.it_interval.tv_sec = static_cast<time_t>(period_ms / 1000u);
its.it_interval.tv_nsec = static_cast<long>((period_ms % 1000u) * 1000000u);
}
if (timerfd_settime(*tfd, 0, &its, nullptr) < 0)
return ScopedPlatformHandle();
return tfd;
#else
ignore_result(args);
return ScopedPlatformHandle();
#endif
}
} // namespace
PeriodicTask::PeriodicTask(TaskRunner* task_runner)
: task_runner_(task_runner), weak_ptr_factory_(this) {}
PeriodicTask::~PeriodicTask() {
Reset();
}
void PeriodicTask::Start(Args args) {
PERFETTO_DCHECK_THREAD(thread_checker_);
Reset();
if (args.period_ms == 0 || !args.task) {
PERFETTO_DCHECK(args.period_ms > 0);
PERFETTO_DCHECK(args.task);
return;
}
args_ = std::move(args);
if (args_.use_suspend_aware_timer) {
timer_fd_ = CreateTimerFd(args_);
if (timer_fd_) {
auto weak_this = weak_ptr_factory_.GetWeakPtr();
task_runner_->AddFileDescriptorWatch(
*timer_fd_,
std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_));
} else {
PERFETTO_DPLOG("timerfd not supported, falling back on PostDelayedTask");
}
} // if (use_suspend_aware_timer).
if (!timer_fd_)
PostNextTask();
if (args_.start_first_task_immediately)
args_.task();
}
void PeriodicTask::PostNextTask() {
PERFETTO_DCHECK_THREAD(thread_checker_);
PERFETTO_DCHECK(args_.period_ms > 0);
PERFETTO_DCHECK(!timer_fd_);
uint32_t delay_ms = GetNextDelayMs(GetWallTimeMs(), args_);
auto weak_this = weak_ptr_factory_.GetWeakPtr();
task_runner_->PostDelayedTask(
std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_),
delay_ms);
}
// static
// This function can be called in two ways (both from the TaskRunner):
// 1. When using a timerfd, this task is registered as a FD watch.
// 2. When using PostDelayedTask, this is the task posted on the TaskRunner.
void PeriodicTask::RunTaskAndPostNext(WeakPtr<PeriodicTask> thiz,
uint32_t generation) {
if (!thiz || !thiz->args_.task || generation != thiz->generation_)
return; // Destroyed or Reset() in the meanwhile.
PERFETTO_DCHECK_THREAD(thiz->thread_checker_);
if (thiz->timer_fd_) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
PERFETTO_FATAL("timerfd for periodic tasks unsupported on Windows");
#else
// If we are using a timerfd there is no need to repeatedly call
// PostDelayedTask(). The kernel will wakeup the timer fd periodically. We
// just need to read() it.
uint64_t ignored = 0;
errno = 0;
auto rsize = Read(*thiz->timer_fd_, &ignored, sizeof(&ignored));
if (rsize != sizeof(uint64_t)) {
if (errno == EAGAIN)
return; // A spurious wakeup. Rare, but can happen, just ignore.
PERFETTO_PLOG("read(timerfd) failed, falling back on PostDelayedTask");
thiz->ResetTimerFd();
}
#endif
}
// Create a copy of the task to deal with either:
// 1. one_shot causing a Reset().
// 2. task() invoking internally Reset().
// That would cause a reset of the args_.task itself, which would invalidate
// the task bind state while we are invoking it.
auto task = thiz->args_.task;
// The repetition of the if() is to deal with the ResetTimerFd() case above.
if (thiz->args_.one_shot) {
thiz->Reset();
} else if (!thiz->timer_fd_) {
thiz->PostNextTask();
}
task();
}
void PeriodicTask::Reset() {
PERFETTO_DCHECK_THREAD(thread_checker_);
++generation_;
args_ = Args();
PERFETTO_DCHECK(!args_.task);
ResetTimerFd();
}
void PeriodicTask::ResetTimerFd() {
if (!timer_fd_)
return;
task_runner_->RemoveFileDescriptorWatch(*timer_fd_);
timer_fd_.reset();
}
} // namespace base
} // namespace perfetto