Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(234)

Side by Side Diff: base/timer/timer.cc

Issue 2491613004: Make base::Timer sequence-friendly. (Closed)
Patch Set: merge up to r442293 Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « base/timer/timer.h ('k') | base/timer/timer_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/timer/timer.h" 5 #include "base/timer/timer.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <utility> 9 #include <utility>
10 10
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/memory/ptr_util.h" 12 #include "base/memory/ptr_util.h"
13 #include "base/memory/ref_counted.h" 13 #include "base/memory/ref_counted.h"
14 #include "base/single_thread_task_runner.h" 14 #include "base/sequenced_task_runner.h"
15 #include "base/threading/platform_thread.h" 15 #include "base/threading/sequenced_task_runner_handle.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/time/tick_clock.h" 16 #include "base/time/tick_clock.h"
18 17
19 namespace base { 18 namespace base {
20 19
21 // BaseTimerTaskInternal is a simple delegate for scheduling a callback to 20 // BaseTimerTaskInternal is a helper for scheduling Timer's callback on the
22 // Timer in the thread's default task runner. It also handles the following 21 // destination task runner (it is in charge of all methods to be executed on
23 // edge cases: 22 // destination task runner while Timer is in charge of all methods to be
24 // - deleted by the task runner. 23 // executed on origin task runner). It owns itself on the destination task
25 // - abandoned (orphaned) by Timer. 24 // runner and supports a few use cases:
26 class BaseTimerTaskInternal { 25 // - Non-repeating timers:
26 // Invokes |stop_callback| on |origin_task_runner| when Run() fires,
27 // *before* running |task|. This ensures that anything posted to origin
28 // task runner from |task| will run in the scope of a stopped task runner
29 // as expected.
30 // Self-destructs when Run() or Abandon() fires, whichever happens first
31 // (any pending Run()/Abandon()/Reset() being cancelled through the
32 // WeakPtr).
33 // - Repeating timers:
34 // Re-enqueues |task| after each invocation of Run() until Abandon() is
35 // invoked (which will invalidate the WeakPtr of the pending Run()).
36 //
37 // An invariant assumed below is that there always is exactly one pending Run()
38 // task associated with this BaseTimerTaskInternal. That invariant is maintained
39 // by having Run() always either triggering self-destruction or reposting self.
40 //
41 // Note: Timer and BaseTimerTaskInternal try to communicate synchronously when
42 // possible (i.e. when SetTaskRunner() wasn't used, which they can verify
43 // through RunsTasksOnCurrentThread()). Timer must also always use asynchronous
44 // calls when invoking Abandon() to avoid calling "delete this" from a reentrant
45 // call while handling Run().
46 class BaseTimerTaskInternal : public SupportsWeakPtr<BaseTimerTaskInternal> {
27 public: 47 public:
28 explicit BaseTimerTaskInternal(Timer* timer) 48 // A helper which invokes a Closure when destroyed. Required to notify
29 : timer_(timer) { 49 // BaseTimerTaskInternal when its Run() task is suppressed before it's
30 } 50 // scheduled (e.g. because the sequence is shutdown). Note: such cleanups
31 51 // can't be bound directly to ~BaseTimerTaskInternal() as it owns itself (its
32 ~BaseTimerTaskInternal() { 52 // tasks use WeakPtrs and deleting them won't trigger
33 // This task may be getting cleared because the task runner has been 53 // ~BaseTimerTaskInternal()).
34 // destructed. If so, don't leave Timer with a dangling pointer 54 class CleanupTrigger {
35 // to this. 55 public:
36 if (timer_) 56 ~CleanupTrigger() { std::move(on_destruction_).Run(); }
37 timer_->StopAndAbandon(); 57
38 } 58 private:
39 59 friend class BaseTimerTaskInternal;
40 void Run() { 60 CleanupTrigger(OnceClosure on_destruction)
41 // timer_ is NULL if we were abandoned. 61 : on_destruction_(std::move(on_destruction)) {}
42 if (!timer_) 62
63 OnceClosure on_destruction_;
64
65 DISALLOW_COPY_AND_ASSIGN(CleanupTrigger);
66 };
67
68 BaseTimerTaskInternal(const tracked_objects::Location& posted_from,
69 const Closure& task,
70 TimeDelta delay,
71 bool is_repeating,
72 TickClock* tick_clock,
73 scoped_refptr<SequencedTaskRunner> origin_task_runner,
74 const Closure& stop_callback)
75 : posted_from_(posted_from),
76 task_(task),
77 delay_(delay),
78 is_repeating_(is_repeating),
79 tick_clock_(tick_clock),
80 desired_run_time_(Now() + delay_),
81 origin_task_runner_(std::move(origin_task_runner)),
82 stop_callback_(stop_callback) {
83 destination_sequence_checker_.DetachFromSequence();
84 }
85
86 ~BaseTimerTaskInternal() = default;
87
88 void Run(std::unique_ptr<CleanupTrigger> cleanup_trigger) {
89 DCHECK(destination_sequence_checker_.CalledOnValidSequence());
90
91 const TimeDelta delay_remaining = desired_run_time_ - Now();
92 if (delay_remaining > TimeDelta::FromMicroseconds(0)) {
93 #if DCHECK_IS_ON()
94 DCHECK(was_reset_) << delay_remaining;
95 #endif
96 SequencedTaskRunnerHandle::Get()->PostDelayedTask(
97 posted_from_, base::Bind(&BaseTimerTaskInternal::Run, AsWeakPtr(),
98 Passed(std::move(cleanup_trigger))),
99 delay_remaining);
43 return; 100 return;
44 101 }
45 // *this will be deleted by the task runner, so Timer needs to 102
46 // forget us: 103 // Trigger cleanup before running the |task_|. e.g. to Stop() OneShotTimers
47 timer_->scheduled_task_ = NULL; 104 // so that anything posted from the task runs in the context of a stopped
48 105 // timer as expected.
49 // Although Timer should not call back into *this, let's clear 106 if (!is_repeating_)
50 // the timer_ member first to be pedantic. 107 cleanup_trigger.reset();
51 Timer* timer = timer_; 108
52 timer_ = NULL; 109 task_.Run();
53 timer->RunScheduledTask(); 110
54 } 111 if (is_repeating_) {
55 112 // TODO(gab): Currently rebasing on Now() to match prior status quo but
56 // The task remains in the MessageLoop queue, but nothing will happen when it 113 // think it would be better to merely |+= delay_| here to avoid the timer
57 // runs. 114 // drifting with the delayed task overhead: http://crbug.com/676836.
115 desired_run_time_ = Now() + delay_;
116 SequencedTaskRunnerHandle::Get()->PostDelayedTask(
117 posted_from_, base::Bind(&BaseTimerTaskInternal::Run, AsWeakPtr(),
118 Passed(std::move(cleanup_trigger))),
119 delay_);
120 #if DCHECK_IS_ON()
121 was_reset_ = false;
122 #endif
123 } else {
124 delete this;
125 }
126 }
127
128 // Postpones the |desired_run_time_| by |delay_|.
129 void Reset() {
130 DCHECK(destination_sequence_checker_.CalledOnValidSequence());
131 DCHECK(!delay_.is_zero());
132
133 // Since Reset() is sequenced with Run() (and cancelled via the WeakPtr if
134 // Run() is scheduled before it -- i.e. delay expires as Reset() is posted):
135 // it is sufficient to merely update |desired_run_time_|.
136 desired_run_time_ = Now() + delay_;
137
138 #if DCHECK_IS_ON()
139 was_reset_ = true;
140 #endif
141 }
142
58 void Abandon() { 143 void Abandon() {
59 timer_ = NULL; 144 DCHECK(destination_sequence_checker_.CalledOnValidSequence());
145
146 // Delete self, invalidating WeakPtrs (and as such cancelling the pending
147 // Run() task).
148 delete this;
149 }
150
151 std::unique_ptr<CleanupTrigger> CreateCleanupTrigger() {
152 // Note: it's important that CleanupTrigger's Closure be bound to a WeakPtr
153 // so that it only triggers if the Run() task is suppressed from its
154 // sequence while it was live (i.e. it shouldn't trigger when the task is
155 // destroyed because the WeakPtr was invalid when an abandoned Run() task
156 // was scheduled).
157 return WrapUnique(
158 new CleanupTrigger(Bind(&BaseTimerTaskInternal::Cleanup, AsWeakPtr())));
60 } 159 }
61 160
62 private: 161 private:
63 Timer* timer_; 162 TimeTicks Now() const {
163 return tick_clock_ ? tick_clock_->NowTicks() : TimeTicks::Now();
164 }
165
166 // Cleans up state on the Timer side when this BaseTimerTaskInternal is done
167 // with its role.
168 void Cleanup() {
169 DCHECK(destination_sequence_checker_.CalledOnValidSequence());
170
171 if (origin_task_runner_->RunsTasksOnCurrentThread()) {
172 stop_callback_.Run();
173 } else {
174 origin_task_runner_->PostTask(FROM_HERE, stop_callback_);
175 }
176 }
177
178 const tracked_objects::Location posted_from_;
179 const Closure task_;
180 const TimeDelta delay_;
181 const bool is_repeating_;
182
183 TickClock* const tick_clock_;
184 TimeTicks desired_run_time_;
185
186 const scoped_refptr<SequencedTaskRunner> origin_task_runner_;
187 const Closure stop_callback_;
188
189 // Verifies that every operation (but construction) happens on the sequence
190 // where the task is intended to run.
191 SequenceChecker destination_sequence_checker_;
192
193 #if DCHECK_IS_ON()
194 // Used to verify that reposts only occur when a Reset() was involved.
195 bool was_reset_ = false;
196 #endif
197
198 DISALLOW_COPY_AND_ASSIGN(BaseTimerTaskInternal);
64 }; 199 };
65 200
66 Timer::Timer(bool retain_user_task, bool is_repeating) 201 Timer::Timer(bool retain_user_task, bool is_repeating)
67 : Timer(retain_user_task, is_repeating, nullptr) {} 202 : Timer(retain_user_task, is_repeating, nullptr) {}
68 203
69 Timer::Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock) 204 Timer::Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock)
70 : scheduled_task_(nullptr), 205 : is_repeating_(is_repeating),
71 thread_id_(0),
72 is_repeating_(is_repeating),
73 retain_user_task_(retain_user_task), 206 retain_user_task_(retain_user_task),
74 tick_clock_(tick_clock), 207 tick_clock_(tick_clock),
75 is_running_(false) {} 208 is_running_(false),
209 weak_ptr_factory_(this) {
210 // It is safe for the timer to be created on a different thread/sequence than
211 // the one from which the timer APIs are called. The first call to the
212 // checker's CalledOnValidSequence() method will re-bind the checker, and
213 // later calls will verify that the same task runner is used.
214 origin_sequence_checker_.DetachFromSequence();
215 }
76 216
77 Timer::Timer(const tracked_objects::Location& posted_from, 217 Timer::Timer(const tracked_objects::Location& posted_from,
78 TimeDelta delay, 218 TimeDelta delay,
79 const base::Closure& user_task, 219 const base::Closure& user_task,
80 bool is_repeating) 220 bool is_repeating)
81 : Timer(posted_from, delay, user_task, is_repeating, nullptr) {} 221 : Timer(posted_from, delay, user_task, is_repeating, nullptr) {}
82 222
83 Timer::Timer(const tracked_objects::Location& posted_from, 223 Timer::Timer(const tracked_objects::Location& posted_from,
84 TimeDelta delay, 224 TimeDelta delay,
85 const base::Closure& user_task, 225 const base::Closure& user_task,
86 bool is_repeating, 226 bool is_repeating,
87 TickClock* tick_clock) 227 TickClock* tick_clock)
88 : scheduled_task_(nullptr), 228 : posted_from_(posted_from),
89 posted_from_(posted_from),
90 delay_(delay), 229 delay_(delay),
91 user_task_(user_task), 230 user_task_(user_task),
92 thread_id_(0),
93 is_repeating_(is_repeating), 231 is_repeating_(is_repeating),
94 retain_user_task_(true), 232 retain_user_task_(true),
95 tick_clock_(tick_clock), 233 tick_clock_(tick_clock),
96 is_running_(false) {} 234 is_running_(false),
235 weak_ptr_factory_(this) {
236 // See comment in other constructor.
237 origin_sequence_checker_.DetachFromSequence();
238 }
97 239
98 Timer::~Timer() { 240 Timer::~Timer() {
99 StopAndAbandon(); 241 // Note: |origin_sequence_checker_| is a SequenceCheckerImpl so this call
242 // is valid in non-DCHECK builds.
243 if (origin_sequence_checker_.CalledOnValidSequence()) {
244 Stop();
245 } else {
246 // As highlighted in the constructor. It's okay to start the Timer on a
247 // different sequence but it must then be sequentially stopped on that
248 // sequence as well before it can be deleted on its original sequence.
249 DCHECK(!is_running_);
250 }
100 } 251 }
101 252
102 bool Timer::IsRunning() const { 253 bool Timer::IsRunning() const {
254 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
103 return is_running_; 255 return is_running_;
104 } 256 }
105 257
106 TimeDelta Timer::GetCurrentDelay() const { 258 TimeDelta Timer::GetCurrentDelay() const {
259 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
107 return delay_; 260 return delay_;
108 } 261 }
109 262
110 void Timer::SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner) { 263 void Timer::SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) {
111 // Do not allow changing the task runner once something has been scheduled. 264 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
112 DCHECK_EQ(thread_id_, 0); 265
113 task_runner_.swap(task_runner); 266 // Do not allow changing the task runner while something is scheduled.
267 DCHECK(!is_running_);
268 destination_task_runner_.swap(task_runner);
114 } 269 }
115 270
116 void Timer::Start(const tracked_objects::Location& posted_from, 271 void Timer::Start(const tracked_objects::Location& posted_from,
117 TimeDelta delay, 272 TimeDelta delay,
118 const base::Closure& user_task) { 273 const base::Closure& user_task) {
119 SetTaskInfo(posted_from, delay, user_task); 274 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
120 Reset(); 275
276 // Do a best-effort to cancel the previous task, it may still racily fire
277 // though.
278 AbandonScheduledTask();
279
280 DCHECK(!is_running_);
281 posted_from_ = posted_from;
282 delay_ = delay;
283 user_task_ = user_task;
284
285 PostNewScheduledTask(delay_);
121 } 286 }
122 287
123 void Timer::Stop() { 288 void Timer::Stop() {
124 is_running_ = false; 289 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
290
291 AbandonScheduledTask();
125 if (!retain_user_task_) 292 if (!retain_user_task_)
126 user_task_.Reset(); 293 user_task_.Reset();
127 } 294 }
128 295
129 void Timer::Reset() { 296 void Timer::Reset() {
297 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
130 DCHECK(!user_task_.is_null()); 298 DCHECK(!user_task_.is_null());
131 299
132 // If there's no pending task, start one up and return. 300 if (is_running_) {
vmpstr 2017/01/12 20:04:22 I kinda prefer if (!is_running_) { PostNewSche
gab 2017/01/25 20:14:56 Agreed, done.
133 if (!scheduled_task_) { 301 if (destination_task_runner_) {
302 destination_task_runner_->PostTask(
303 FROM_HERE,
304 base::Bind(&BaseTimerTaskInternal::Reset, scheduled_task_weak_ref_));
305 } else {
306 // |is_running_| guarantees a valid pointer when not using an external
307 // |destination_task_runner_|.
308 DCHECK(scheduled_task_weak_ref_);
309 scheduled_task_weak_ref_->Reset();
310 }
311 } else {
312 // This is required to handle the following use case (when
313 // |retain_user_task_ ==true|): Start() => Stop() => Reset().
134 PostNewScheduledTask(delay_); 314 PostNewScheduledTask(delay_);
135 return;
136 } 315 }
137
138 // Set the new desired_run_time_.
139 if (delay_ > TimeDelta::FromMicroseconds(0))
140 desired_run_time_ = Now() + delay_;
141 else
142 desired_run_time_ = TimeTicks();
143
144 // We can use the existing scheduled task if it arrives before the new
145 // desired_run_time_.
146 if (desired_run_time_ >= scheduled_run_time_) {
147 is_running_ = true;
148 return;
149 }
150
151 // We can't reuse the scheduled_task_, so abandon it and post a new one.
152 AbandonScheduledTask();
153 PostNewScheduledTask(delay_);
154 }
155
156 TimeTicks Timer::Now() const {
157 return tick_clock_ ? tick_clock_->NowTicks() : TimeTicks::Now();
158 }
159
160 void Timer::SetTaskInfo(const tracked_objects::Location& posted_from,
161 TimeDelta delay,
162 const base::Closure& user_task) {
163 posted_from_ = posted_from;
164 delay_ = delay;
165 user_task_ = user_task;
166 } 316 }
167 317
168 void Timer::PostNewScheduledTask(TimeDelta delay) { 318 void Timer::PostNewScheduledTask(TimeDelta delay) {
169 DCHECK(scheduled_task_ == NULL); 319 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
320 DCHECK(SequencedTaskRunnerHandle::IsSet());
321 DCHECK(!is_running_);
322
323 scoped_refptr<SequencedTaskRunner> destination_task_runner =
vmpstr 2017/01/12 20:04:22 Do you need a ref here? Can it be SequencedTaskRun
gab 2017/01/25 20:14:56 I had the same thought and had originally written
324 destination_task_runner_ ? destination_task_runner_
325 : SequencedTaskRunnerHandle::Get();
326
170 is_running_ = true; 327 is_running_ = true;
171 scheduled_task_ = new BaseTimerTaskInternal(this); 328 BaseTimerTaskInternal* scheduled_task = new BaseTimerTaskInternal(
vmpstr 2017/01/12 20:04:22 Can you add a comment saying that the task will de
gab 2017/01/25 20:14:56 Done.
172 if (delay > TimeDelta::FromMicroseconds(0)) { 329 posted_from_, user_task_, delay, is_repeating_, tick_clock_,
173 GetTaskRunner()->PostDelayedTask(posted_from_, 330 SequencedTaskRunnerHandle::Get(),
174 base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)), 331 base::Bind(&Timer::Stop, weak_ptr_factory_.GetWeakPtr()));
175 delay); 332 scheduled_task_weak_ref_ = scheduled_task->AsWeakPtr();
176 scheduled_run_time_ = desired_run_time_ = Now() + delay; 333 destination_task_runner->PostDelayedTask(
177 } else { 334 posted_from_,
178 GetTaskRunner()->PostTask(posted_from_, 335 base::Bind(&BaseTimerTaskInternal::Run, scheduled_task_weak_ref_,
179 base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_))); 336 Passed(scheduled_task->CreateCleanupTrigger())),
180 scheduled_run_time_ = desired_run_time_ = TimeTicks(); 337 delay);
181 }
182 // Remember the thread ID that posts the first task -- this will be verified
183 // later when the task is abandoned to detect misuse from multiple threads.
184 if (!thread_id_)
185 thread_id_ = static_cast<int>(PlatformThread::CurrentId());
186 }
187
188 scoped_refptr<SingleThreadTaskRunner> Timer::GetTaskRunner() {
189 return task_runner_.get() ? task_runner_ : ThreadTaskRunnerHandle::Get();
190 } 338 }
191 339
192 void Timer::AbandonScheduledTask() { 340 void Timer::AbandonScheduledTask() {
193 DCHECK(thread_id_ == 0 || 341 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
194 thread_id_ == static_cast<int>(PlatformThread::CurrentId())); 342
195 if (scheduled_task_) { 343 if (is_running_) {
vmpstr 2017/01/12 20:04:22 if (!is_running_) return; ...
gab 2017/01/25 20:14:56 Done.
196 scheduled_task_->Abandon(); 344 // Toggle |is_running| first to be reentrancy safe, just in case.
197 scheduled_task_ = NULL; 345 is_running_ = false;
346
347 const Closure abandon =
348 base::Bind(&BaseTimerTaskInternal::Abandon, scheduled_task_weak_ref_);
349 if (destination_task_runner_) {
350 destination_task_runner_->PostTask(FROM_HERE, abandon);
351 } else if (SequencedTaskRunnerHandle::IsSet()) {
352 // Post Abandon() as an asynchronous call to avoid deleting
353 // BaseTimerTaskInternal while it handles Run() (reentrancy is possible,
354 // e.g. when the task itself deletes the Timer or when a OneShotTimer's
355 // BaseTimerTaskInternal::Run() invoked its |stop_callback_|
356 // synchronously).
357 SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, abandon);
358 } else {
359 // |is_running_| guarantees a valid pointer when not using an external
360 // |destination_task_runner_|.
361 DCHECK(scheduled_task_weak_ref_);
362 // The reentrancy situations described above happen while Run() is active
363 // which implies SequencedTaskRunnerHandle::IsSet().
364 // SequencedTaskRunnerHandle not being set typically means this
365 // AbandonScheduledTask() was triggered by ~Timer() on shutdown per the
366 // sequence being destroyed and releasing unscheduled tasks in which case
367 // it's safe to abandon synchronously.
368 scheduled_task_weak_ref_->Abandon();
369 }
370 scheduled_task_weak_ref_ = nullptr;
198 } 371 }
199 } 372 }
200 373
201 void Timer::RunScheduledTask() {
202 // Task may have been disabled.
203 if (!is_running_)
204 return;
205
206 // First check if we need to delay the task because of a new target time.
207 if (desired_run_time_ > scheduled_run_time_) {
208 // Now() can be expensive, so only call it if we know the user has changed
209 // the desired_run_time_.
210 TimeTicks now = Now();
211 // Task runner may have called us late anyway, so only post a continuation
212 // task if the desired_run_time_ is in the future.
213 if (desired_run_time_ > now) {
214 // Post a new task to span the remaining time.
215 PostNewScheduledTask(desired_run_time_ - now);
216 return;
217 }
218 }
219
220 // Make a local copy of the task to run. The Stop method will reset the
221 // user_task_ member if retain_user_task_ is false.
222 base::Closure task = user_task_;
223
224 if (is_repeating_)
225 PostNewScheduledTask(delay_);
226 else
227 Stop();
228
229 task.Run();
230
231 // No more member accesses here: *this could be deleted at this point.
232 }
233
234 } // namespace base 374 } // namespace base
OLDNEW
« no previous file with comments | « base/timer/timer.h ('k') | base/timer/timer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698