/* * 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 #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 #endif namespace perfetto { namespace base { namespace { base::ScopedPlatformHandle CreateTimerFd(uint32_t period_ms) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19) base::ScopedPlatformHandle tfd( timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK)); // The initial phase, aligned on wall clock. uint32_t phase_ms = period_ms - static_cast(base::GetBootTimeNs().count() % period_ms); 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(phase_ms / 1000u); its.it_value.tv_nsec = 1 + static_cast((phase_ms % 1000u) * 1000000u); its.it_interval.tv_sec = static_cast(period_ms / 1000u); its.it_interval.tv_nsec = static_cast((period_ms % 1000u) * 1000000u); if (timerfd_settime(*tfd, 0, &its, nullptr) < 0) return base::ScopedPlatformHandle(); return tfd; #else base::ignore_result(period_ms); return base::ScopedPlatformHandle(); #endif } } // namespace PeriodicTask::PeriodicTask(base::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_.period_ms); 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 = args_.period_ms - static_cast(base::GetWallTimeMs().count() % args_.period_ms); 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(base::WeakPtr 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 = base::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 } // The repetition of the if() is to deal with the ResetTimerFd() case above. if (!thiz->timer_fd_) { thiz->PostNextTask(); } // Create a copy of the task in the unlikely event that the task ends up // up destroying the PeriodicTask object or calling Reset() on it. 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; 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