• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "perfetto/ext/base/periodic_task.h"
18 
19 #include <limits>
20 
21 #include "perfetto/base/build_config.h"
22 #include "perfetto/base/logging.h"
23 #include "perfetto/base/task_runner.h"
24 #include "perfetto/base/time.h"
25 #include "perfetto/ext/base/file_utils.h"
26 
27 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
28     (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
29 #include <sys/timerfd.h>
30 #endif
31 
32 namespace perfetto {
33 namespace base {
34 
35 namespace {
CreateTimerFd(uint32_t period_ms)36 base::ScopedPlatformHandle CreateTimerFd(uint32_t period_ms) {
37 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
38     (PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) && __ANDROID_API__ >= 19)
39   base::ScopedPlatformHandle tfd(
40       timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK));
41   // The initial phase, aligned on wall clock.
42   uint32_t phase_ms =
43       period_ms -
44       static_cast<uint32_t>(base::GetBootTimeNs().count() % period_ms);
45   struct itimerspec its {};
46   // The "1 +" is to make sure that we never pass a zero it_value in the
47   // unlikely case of phase_ms being 0. That would cause the timer to be
48   // considered disarmed by timerfd_settime.
49   its.it_value.tv_sec = static_cast<time_t>(phase_ms / 1000u);
50   its.it_value.tv_nsec = 1 + static_cast<long>((phase_ms % 1000u) * 1000000u);
51   its.it_interval.tv_sec = static_cast<time_t>(period_ms / 1000u);
52   its.it_interval.tv_nsec = static_cast<long>((period_ms % 1000u) * 1000000u);
53   if (timerfd_settime(*tfd, 0, &its, nullptr) < 0)
54     return base::ScopedPlatformHandle();
55   return tfd;
56 #else
57   base::ignore_result(period_ms);
58   return base::ScopedPlatformHandle();
59 #endif
60 }
61 }  // namespace
62 
PeriodicTask(base::TaskRunner * task_runner)63 PeriodicTask::PeriodicTask(base::TaskRunner* task_runner)
64     : task_runner_(task_runner), weak_ptr_factory_(this) {}
65 
~PeriodicTask()66 PeriodicTask::~PeriodicTask() {
67   Reset();
68 }
69 
Start(Args args)70 void PeriodicTask::Start(Args args) {
71   PERFETTO_DCHECK_THREAD(thread_checker_);
72   Reset();
73   if (args.period_ms == 0 || !args.task) {
74     PERFETTO_DCHECK(args.period_ms > 0);
75     PERFETTO_DCHECK(args.task);
76     return;
77   }
78   args_ = std::move(args);
79   if (args_.use_suspend_aware_timer) {
80     timer_fd_ = CreateTimerFd(args_.period_ms);
81     if (timer_fd_) {
82       auto weak_this = weak_ptr_factory_.GetWeakPtr();
83       task_runner_->AddFileDescriptorWatch(
84           *timer_fd_,
85           std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_));
86     } else {
87       PERFETTO_DPLOG("timerfd not supported, falling back on PostDelayedTask");
88     }
89   }  // if (use_suspend_aware_timer).
90 
91   if (!timer_fd_)
92     PostNextTask();
93 
94   if (args_.start_first_task_immediately)
95     args_.task();
96 }
97 
PostNextTask()98 void PeriodicTask::PostNextTask() {
99   PERFETTO_DCHECK_THREAD(thread_checker_);
100   PERFETTO_DCHECK(args_.period_ms > 0);
101   PERFETTO_DCHECK(!timer_fd_);
102   uint32_t delay_ms =
103       args_.period_ms -
104       static_cast<uint32_t>(base::GetWallTimeMs().count() % args_.period_ms);
105   auto weak_this = weak_ptr_factory_.GetWeakPtr();
106   task_runner_->PostDelayedTask(
107       std::bind(PeriodicTask::RunTaskAndPostNext, weak_this, generation_),
108       delay_ms);
109 }
110 
111 // static
112 // This function can be called in two ways (both from the TaskRunner):
113 // 1. When using a timerfd, this task is registered as a FD watch.
114 // 2. When using PostDelayedTask, this is the task posted on the TaskRunner.
RunTaskAndPostNext(base::WeakPtr<PeriodicTask> thiz,uint32_t generation)115 void PeriodicTask::RunTaskAndPostNext(base::WeakPtr<PeriodicTask> thiz,
116                                       uint32_t generation) {
117   if (!thiz || !thiz->args_.task || generation != thiz->generation_)
118     return;  // Destroyed or Reset() in the meanwhile.
119   PERFETTO_DCHECK_THREAD(thiz->thread_checker_);
120   if (thiz->timer_fd_) {
121 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
122     PERFETTO_FATAL("timerfd for periodic tasks unsupported on Windows");
123 #else
124     // If we are using a timerfd there is no need to repeatedly call
125     // PostDelayedTask(). The kernel will wakeup the timer fd periodically. We
126     // just need to read() it.
127     uint64_t ignored = 0;
128     errno = 0;
129     auto rsize = base::Read(*thiz->timer_fd_, &ignored, sizeof(&ignored));
130     if (rsize != sizeof(uint64_t)) {
131       if (errno == EAGAIN)
132         return;  // A spurious wakeup. Rare, but can happen, just ignore.
133       PERFETTO_PLOG("read(timerfd) failed, falling back on PostDelayedTask");
134       thiz->ResetTimerFd();
135     }
136 #endif
137   }
138   // The repetition of the if() is to deal with the ResetTimerFd() case above.
139   if (!thiz->timer_fd_) {
140     thiz->PostNextTask();
141   }
142   // Create a copy of the task in the unlikely event that the task ends up
143   // up destroying the PeriodicTask object or calling Reset() on it. That would
144   // cause a reset of the args_.task itself, which would invalidate the task
145   // bind state while we are invoking it.
146   auto task = thiz->args_.task;
147   task();
148 }
149 
Reset()150 void PeriodicTask::Reset() {
151   PERFETTO_DCHECK_THREAD(thread_checker_);
152   ++generation_;
153   args_ = Args();
154   PERFETTO_DCHECK(!args_.task);
155   ResetTimerFd();
156 }
157 
ResetTimerFd()158 void PeriodicTask::ResetTimerFd() {
159   if (!timer_fd_)
160     return;
161   task_runner_->RemoveFileDescriptorWatch(*timer_fd_);
162   timer_fd_.reset();
163 }
164 
165 }  // namespace base
166 }  // namespace perfetto
167