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

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

Issue 2491613004: Make base::Timer sequence-friendly. (Closed)
Patch Set: rebase on r464476 Created 3 years, 8 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
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"
15 #include "base/threading/platform_thread.h" 14 #include "base/threading/platform_thread.h"
16 #include "base/threading/thread_task_runner_handle.h" 15 #include "base/threading/sequenced_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 simple delegate for scheduling a callback to Timer
22 // Timer in the thread's default task runner. It also handles the following 21 // on the current sequence. It also handles the following edge cases:
23 // edge cases:
24 // - deleted by the task runner. 22 // - deleted by the task runner.
25 // - abandoned (orphaned) by Timer. 23 // - abandoned (orphaned) by Timer.
26 class BaseTimerTaskInternal { 24 class BaseTimerTaskInternal {
27 public: 25 public:
28 explicit BaseTimerTaskInternal(Timer* timer) 26 explicit BaseTimerTaskInternal(Timer* timer)
29 : timer_(timer) { 27 : timer_(timer) {
30 } 28 }
31 29
32 ~BaseTimerTaskInternal() { 30 ~BaseTimerTaskInternal() {
33 // This task may be getting cleared because the task runner has been 31 // This task may be getting cleared because the task runner has been
34 // destructed. If so, don't leave Timer with a dangling pointer 32 // destructed. If so, don't leave Timer with a dangling pointer
35 // to this. 33 // to this.
36 if (timer_) 34 if (timer_)
37 timer_->StopAndAbandon(); 35 timer_->Stop();
38 } 36 }
39 37
40 void Run() { 38 void Run() {
41 // timer_ is NULL if we were abandoned. 39 // |timer_| is nullptr if we were abandoned.
42 if (!timer_) 40 if (!timer_)
43 return; 41 return;
44 42
45 // *this will be deleted by the task runner, so Timer needs to 43 // |this| will be deleted by the task runner, so Timer needs to forget us:
46 // forget us: 44 timer_->scheduled_task_ = nullptr;
47 timer_->scheduled_task_ = NULL;
48 45
49 // Although Timer should not call back into *this, let's clear 46 // Although Timer should not call back into |this|, let's clear |timer_|
50 // the timer_ member first to be pedantic. 47 // first to be pedantic.
51 Timer* timer = timer_; 48 Timer* timer = timer_;
52 timer_ = NULL; 49 timer_ = nullptr;
53 timer->RunScheduledTask(); 50 timer->RunScheduledTask();
54 } 51 }
55 52
56 // The task remains in the MessageLoop queue, but nothing will happen when it 53 // The task remains in the queue, but nothing will happen when it runs.
57 // runs. 54 void Abandon() { timer_ = nullptr; }
58 void Abandon() {
59 timer_ = NULL;
60 }
61 55
62 private: 56 private:
63 Timer* timer_; 57 Timer* timer_;
58
59 DISALLOW_COPY_AND_ASSIGN(BaseTimerTaskInternal);
64 }; 60 };
65 61
66 Timer::Timer(bool retain_user_task, bool is_repeating) 62 Timer::Timer(bool retain_user_task, bool is_repeating)
67 : Timer(retain_user_task, is_repeating, nullptr) {} 63 : Timer(retain_user_task, is_repeating, nullptr) {}
68 64
69 Timer::Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock) 65 Timer::Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock)
70 : scheduled_task_(nullptr), 66 : scheduled_task_(nullptr),
71 thread_id_(0),
72 is_repeating_(is_repeating), 67 is_repeating_(is_repeating),
73 retain_user_task_(retain_user_task), 68 retain_user_task_(retain_user_task),
74 tick_clock_(tick_clock), 69 tick_clock_(tick_clock),
75 is_running_(false) {} 70 is_running_(false) {
71 // It is safe for the timer to be created on a different thread/sequence than
72 // the one from which the timer APIs are called. The first call to the
73 // checker's CalledOnValidSequence() method will re-bind the checker, and
74 // later calls will verify that the same task runner is used.
75 origin_sequence_checker_.DetachFromSequence();
76 }
76 77
77 Timer::Timer(const tracked_objects::Location& posted_from, 78 Timer::Timer(const tracked_objects::Location& posted_from,
78 TimeDelta delay, 79 TimeDelta delay,
79 const base::Closure& user_task, 80 const base::Closure& user_task,
80 bool is_repeating) 81 bool is_repeating)
81 : Timer(posted_from, delay, user_task, is_repeating, nullptr) {} 82 : Timer(posted_from, delay, user_task, is_repeating, nullptr) {}
82 83
83 Timer::Timer(const tracked_objects::Location& posted_from, 84 Timer::Timer(const tracked_objects::Location& posted_from,
84 TimeDelta delay, 85 TimeDelta delay,
85 const base::Closure& user_task, 86 const base::Closure& user_task,
86 bool is_repeating, 87 bool is_repeating,
87 TickClock* tick_clock) 88 TickClock* tick_clock)
88 : scheduled_task_(nullptr), 89 : scheduled_task_(nullptr),
89 posted_from_(posted_from), 90 posted_from_(posted_from),
90 delay_(delay), 91 delay_(delay),
91 user_task_(user_task), 92 user_task_(user_task),
92 thread_id_(0),
93 is_repeating_(is_repeating), 93 is_repeating_(is_repeating),
94 retain_user_task_(true), 94 retain_user_task_(true),
95 tick_clock_(tick_clock), 95 tick_clock_(tick_clock),
96 is_running_(false) {} 96 is_running_(false) {
97 // See comment in other constructor.
98 origin_sequence_checker_.DetachFromSequence();
99 }
97 100
98 Timer::~Timer() { 101 Timer::~Timer() {
99 StopAndAbandon(); 102 // As highlighted in the constructor. It's okay to start the Timer on a
103 // different sequence but it must then be sequentially stopped on that
104 // sequence as well before it can be deleted on its owning sequence.
105 DCHECK(origin_sequence_checker_.CalledOnValidSequence() || !is_running_);
106
107 // Don't call Stop() if |!origin_sequence_checker_.CalledOnValidSequence()|
108 // (which, per the above, implies |!is_running|) -- there shouldn't be a
109 // pending |scheduled_task_| pointing at |this| if |!is_running|.
110 DCHECK(is_running_ || !scheduled_task_);
111 if (is_running_)
112 Stop();
100 } 113 }
101 114
102 bool Timer::IsRunning() const { 115 bool Timer::IsRunning() const {
116 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
103 return is_running_; 117 return is_running_;
104 } 118 }
105 119
106 TimeDelta Timer::GetCurrentDelay() const { 120 TimeDelta Timer::GetCurrentDelay() const {
121 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
107 return delay_; 122 return delay_;
108 } 123 }
109 124
110 void Timer::SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner) { 125 void Timer::SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) {
111 // Do not allow changing the task runner once something has been scheduled. 126 // Do not allow changing the task runner once something has been scheduled.
fdoray 2017/05/10 14:22:58 ... when the Timer is running. Because it is poss
gab 2017/05/26 04:26:57 Done.
112 DCHECK_EQ(thread_id_, 0); 127 // Don't check for |origin_sequence_checker_.CalledOnValidSequence()| here to
128 // allow the use case of constructing the Timer and immediatetly invoking
129 // SetTaskRunner() before starting it (CalledOnValidSequence() would undo the
130 // DetachFromSequence() from the constructor). The |!is_running| check kind of
131 // verifies the same thing (and TSAN should catch callers that do it wrong but
132 // somehow evade all debug checks).
133 DCHECK(!is_running_);
113 task_runner_.swap(task_runner); 134 task_runner_.swap(task_runner);
114 } 135 }
115 136
116 void Timer::Start(const tracked_objects::Location& posted_from, 137 void Timer::Start(const tracked_objects::Location& posted_from,
117 TimeDelta delay, 138 TimeDelta delay,
118 const base::Closure& user_task) { 139 const base::Closure& user_task) {
119 SetTaskInfo(posted_from, delay, user_task); 140 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
141
142 posted_from_ = posted_from;
143 delay_ = delay;
144 user_task_ = user_task;
145
120 Reset(); 146 Reset();
121 } 147 }
122 148
123 void Timer::Stop() { 149 void Timer::Stop() {
150 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
151
152 // While a pending scheduled task could in theory be left in the queue in the
153 // hope of recycling it should the Timer be restarted before it fires, it's
154 // simpler to unconditionally abandon it as it helps avoid races between it
155 // and ~Timer() should its owning sequence (where it's constructed/destroyed)
156 // differ from its running sequence (where it was started -- bound to
157 // |origin_sequence_checker_|). Hopefully repeated fast start/stop of the
158 // same Timer is rare...
159 // Note: It's also important to abandon before |user_task_.Reset()| below as
160 // |user_task_| may indirectly own this Timer.
161 AbandonScheduledTask();
162
124 is_running_ = false; 163 is_running_ = false;
125 if (!retain_user_task_) 164 if (!retain_user_task_)
126 user_task_.Reset(); 165 user_task_.Reset();
166 // No more member accesses here: |this| could be deleted after freeing
167 // |user_task_|.
127 } 168 }
128 169
129 void Timer::Reset() { 170 void Timer::Reset() {
171 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
130 DCHECK(!user_task_.is_null()); 172 DCHECK(!user_task_.is_null());
131 173
132 // If there's no pending task, start one up and return. 174 // If there's no pending task, start one up and return.
133 if (!scheduled_task_) { 175 if (!scheduled_task_) {
134 PostNewScheduledTask(delay_); 176 PostNewScheduledTask(delay_);
135 return; 177 return;
136 } 178 }
137 179
138 // Set the new desired_run_time_. 180 // Set the new |desired_run_time_|.
139 if (delay_ > TimeDelta::FromMicroseconds(0)) 181 if (delay_ > TimeDelta::FromMicroseconds(0))
140 desired_run_time_ = Now() + delay_; 182 desired_run_time_ = Now() + delay_;
141 else 183 else
142 desired_run_time_ = TimeTicks(); 184 desired_run_time_ = TimeTicks();
143 185
144 // We can use the existing scheduled task if it arrives before the new 186 // We can use the existing scheduled task if it arrives before the new
fdoray 2017/05/10 14:22:58 In a world where TaskScheduler is able to ignore c
gab 2017/05/26 04:26:57 I agree, but that's a behaviour change that belong
145 // desired_run_time_. 187 // |desired_run_time_|.
146 if (desired_run_time_ >= scheduled_run_time_) { 188 if (desired_run_time_ >= scheduled_run_time_) {
147 is_running_ = true; 189 is_running_ = true;
148 return; 190 return;
149 } 191 }
150 192
151 // We can't reuse the scheduled_task_, so abandon it and post a new one. 193 // We can't reuse the |scheduled_task_|, so abandon it and post a new one.
152 AbandonScheduledTask(); 194 AbandonScheduledTask();
153 PostNewScheduledTask(delay_); 195 PostNewScheduledTask(delay_);
154 } 196 }
155 197
156 TimeTicks Timer::Now() const { 198 TimeTicks Timer::Now() const {
199 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
157 return tick_clock_ ? tick_clock_->NowTicks() : TimeTicks::Now(); 200 return tick_clock_ ? tick_clock_->NowTicks() : TimeTicks::Now();
158 } 201 }
159 202
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 }
167
168 void Timer::PostNewScheduledTask(TimeDelta delay) { 203 void Timer::PostNewScheduledTask(TimeDelta delay) {
169 DCHECK(scheduled_task_ == NULL); 204 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
205 DCHECK(!scheduled_task_);
170 is_running_ = true; 206 is_running_ = true;
171 scheduled_task_ = new BaseTimerTaskInternal(this); 207 scheduled_task_ = new BaseTimerTaskInternal(this);
fdoray 2017/05/10 14:22:58 Instead of having a BaseTimerTaskInternal, simply
gab 2017/05/26 04:26:57 Again, this can definitely be improved with WeakPt
172 if (delay > TimeDelta::FromMicroseconds(0)) { 208 if (delay > TimeDelta::FromMicroseconds(0)) {
173 GetTaskRunner()->PostDelayedTask( 209 SequencedTaskRunnerHandle::Get()->PostDelayedTask(
174 posted_from_, 210 posted_from_,
175 base::BindOnce(&BaseTimerTaskInternal::Run, 211 base::BindOnce(&BaseTimerTaskInternal::Run,
176 base::Owned(scheduled_task_)), 212 base::Owned(scheduled_task_)),
177 delay); 213 delay);
178 scheduled_run_time_ = desired_run_time_ = Now() + delay; 214 scheduled_run_time_ = desired_run_time_ = Now() + delay;
179 } else { 215 } else {
180 GetTaskRunner()->PostTask(posted_from_, 216 SequencedTaskRunnerHandle::Get()->PostTask(
181 base::BindOnce(&BaseTimerTaskInternal::Run, 217 posted_from_, base::BindOnce(&BaseTimerTaskInternal::Run,
182 base::Owned(scheduled_task_))); 218 base::Owned(scheduled_task_)));
183 scheduled_run_time_ = desired_run_time_ = TimeTicks(); 219 scheduled_run_time_ = desired_run_time_ = TimeTicks();
184 } 220 }
185 // Remember the thread ID that posts the first task -- this will be verified
186 // later when the task is abandoned to detect misuse from multiple threads.
187 if (!thread_id_)
188 thread_id_ = static_cast<int>(PlatformThread::CurrentId());
189 }
190
191 scoped_refptr<SingleThreadTaskRunner> Timer::GetTaskRunner() {
192 return task_runner_.get() ? task_runner_ : ThreadTaskRunnerHandle::Get();
193 } 221 }
194 222
195 void Timer::AbandonScheduledTask() { 223 void Timer::AbandonScheduledTask() {
196 DCHECK(thread_id_ == 0 || 224 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
197 thread_id_ == static_cast<int>(PlatformThread::CurrentId()));
198 if (scheduled_task_) { 225 if (scheduled_task_) {
199 scheduled_task_->Abandon(); 226 scheduled_task_->Abandon();
200 scheduled_task_ = NULL; 227 scheduled_task_ = nullptr;
201 } 228 }
202 } 229 }
203 230
204 void Timer::RunScheduledTask() { 231 void Timer::RunScheduledTask() {
232 DCHECK(origin_sequence_checker_.CalledOnValidSequence());
233
205 // Task may have been disabled. 234 // Task may have been disabled.
206 if (!is_running_) 235 if (!is_running_)
207 return; 236 return;
208 237
209 // First check if we need to delay the task because of a new target time. 238 // First check if we need to delay the task because of a new target time.
210 if (desired_run_time_ > scheduled_run_time_) { 239 if (desired_run_time_ > scheduled_run_time_) {
211 // Now() can be expensive, so only call it if we know the user has changed 240 // Now() can be expensive, so only call it if we know the user has changed
212 // the desired_run_time_. 241 // the |desired_run_time_|.
213 TimeTicks now = Now(); 242 TimeTicks now = Now();
214 // Task runner may have called us late anyway, so only post a continuation 243 // Task runner may have called us late anyway, so only post a continuation
215 // task if the desired_run_time_ is in the future. 244 // task if the |desired_run_time_| is in the future.
216 if (desired_run_time_ > now) { 245 if (desired_run_time_ > now) {
217 // Post a new task to span the remaining time. 246 // Post a new task to span the remaining time.
218 PostNewScheduledTask(desired_run_time_ - now); 247 PostNewScheduledTask(desired_run_time_ - now);
219 return; 248 return;
220 } 249 }
221 } 250 }
222 251
223 // Make a local copy of the task to run. The Stop method will reset the 252 // Make a local copy of the task to run. The Stop method will reset the
224 // user_task_ member if retain_user_task_ is false. 253 // |user_task_| member if |retain_user_task_| is false.
225 base::Closure task = user_task_; 254 base::Closure task = user_task_;
226 255
227 if (is_repeating_) 256 if (is_repeating_)
228 PostNewScheduledTask(delay_); 257 PostNewScheduledTask(delay_);
229 else 258 else
230 Stop(); 259 Stop();
231 260
232 task.Run(); 261 if (task_runner_ && !task_runner_->RunsTasksOnCurrentThread())
262 task_runner_->PostTask(posted_from_, std::move(task));
263 else
264 task.Run();
233 265
234 // No more member accesses here: *this could be deleted at this point. 266 // No more member accesses here: |this| could be deleted at this point.
235 } 267 }
236 268
237 } // namespace base 269 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698