aboutsummaryrefslogtreecommitdiff
path: root/src/base/periodic_task.cc
blob: fcbc9de56e4ee6ebab5c197769052bfae1444a44 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 * 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 {
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<uint32_t>(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<time_t>(phase_ms / 1000u);
  its.it_value.tv_nsec = 1 + static_cast<long>((phase_ms % 1000u) * 1000000u);
  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 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<uint32_t>(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<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 = 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