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

Side by Side Diff: base/task_scheduler/task_tracker.cc

Issue 2019763002: TaskScheduler: Atomic operations in TaskTracker (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 6 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 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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/task_scheduler/task_tracker.h" 5 #include "base/task_scheduler/task_tracker.h"
6 6
7 #include "base/atomicops.h"
7 #include "base/callback.h" 8 #include "base/callback.h"
8 #include "base/debug/task_annotator.h" 9 #include "base/debug/task_annotator.h"
9 #include "base/metrics/histogram_macros.h" 10 #include "base/metrics/histogram_macros.h"
10 #include "base/threading/sequenced_task_runner_handle.h" 11 #include "base/threading/sequenced_task_runner_handle.h"
11 #include "base/threading/thread_restrictions.h" 12 #include "base/threading/thread_restrictions.h"
12 #include "base/threading/thread_task_runner_handle.h" 13 #include "base/threading/thread_task_runner_handle.h"
13 #include "base/trace_event/trace_event.h" 14 #include "base/trace_event/trace_event.h"
14 15
15 namespace base { 16 namespace base {
16 namespace internal { 17 namespace internal {
(...skipping 12 matching lines...) Expand all
29 30
30 void RecordNumBlockShutdownTasksPostedDuringShutdown( 31 void RecordNumBlockShutdownTasksPostedDuringShutdown(
31 HistogramBase::Sample value) { 32 HistogramBase::Sample value) {
32 UMA_HISTOGRAM_CUSTOM_COUNTS( 33 UMA_HISTOGRAM_CUSTOM_COUNTS(
33 "TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1, 34 "TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1,
34 kMaxBlockShutdownTasksPostedDuringShutdown, 50); 35 kMaxBlockShutdownTasksPostedDuringShutdown, 50);
35 } 36 }
36 37
37 } // namespace 38 } // namespace
38 39
39 TaskTracker::TaskTracker() = default; 40 class TaskTracker::State {
41 public:
42 State() = default;
43
44 // Sets a flag indicating that shutdown has started.
45 void StartShutdown() {
46 subtle::Barrier_AtomicIncrement(&bits_, kShutdownHasStartedMask);
robliao 2016/05/27 22:16:14 If this gets called twice, doesn't that mean the L
fdoray 2016/05/30 15:48:04 Yes. StartShutdown() can't be called twice. Added
47 }
48
49 // Returns true if shutdown has started.
50 bool ShutdownHasStarted() const {
51 return subtle::NoBarrier_Load(&bits_) & kShutdownHasStartedMask;
52 }
53
54 // Returns true if there are tasks blocking shutdown.
55 bool TasksAreBlockingShutdown() const {
56 const auto num_tasks_blocking_shutdown =
57 subtle::NoBarrier_Load(&bits_) / kNumTasksBlockingShutdownIncrement;
58 DCHECK_GE(num_tasks_blocking_shutdown, 0);
59 return num_tasks_blocking_shutdown != 0;
60 }
61
62 // Increments the number of tasks blocking shutdown. Returns true if shutdown
63 // has started.
64 bool IncrementNumTasksBlockingShutdown() {
65 const auto new_bits = subtle::NoBarrier_AtomicIncrement(
66 &bits_, kNumTasksBlockingShutdownIncrement);
67
68 // Verify that there is no overflow.
69 const auto num_tasks_blocking_shutdown =
70 new_bits / kNumTasksBlockingShutdownIncrement;
71 DCHECK_GE(num_tasks_blocking_shutdown, 0);
72
73 const bool shutdown_has_started = new_bits & kShutdownHasStartedMask;
74 return shutdown_has_started;
75 }
76
77 // Decrements the number of tasks blocking shutdown. Returns true if shutdown
78 // has started and the number of tasks blocking shutdown becomes zero.
79 bool DecrementNumTasksBlockingShutdown() {
80 const auto new_bits = subtle::NoBarrier_AtomicIncrement(
robliao 2016/05/27 22:16:14 Is this safe without a barrier? If the atomic incr
fdoray 2016/05/30 15:48:04 I believe a barrier "specifies how regular, non-at
robliao 2016/06/01 18:32:26 Our usage is not unlike shared_ptr refcounts. From
fdoray 2016/06/06 16:43:35 Are you suggesting that I use Barrier_AtomicIncrem
robliao 2016/06/06 19:16:30 Yup, because it seems like we'll have a race witho
fdoray 2016/06/16 13:19:37 Do you worry about increment on thread A occurring
robliao 2016/06/16 20:40:45 I'm considering the atomics in isolation to the re
fdoray 2016/06/17 20:28:49 I'm not against adding a memory barrier... but it
robliao 2016/06/18 00:10:27 Oddly enough, I think the same thing can happen in
81 &bits_, -kNumTasksBlockingShutdownIncrement);
82 const bool shutdown_has_started = new_bits & kShutdownHasStartedMask;
83 const auto num_tasks_blocking_shutdown =
84 new_bits / kNumTasksBlockingShutdownIncrement;
85 DCHECK_GE(num_tasks_blocking_shutdown, 0);
86 return shutdown_has_started && num_tasks_blocking_shutdown == 0;
87 }
88
89 private:
90 static constexpr subtle::Atomic32 kShutdownHasStartedMask = 1;
91 static constexpr subtle::Atomic32 kNumTasksBlockingShutdownIncrement = 2;
92
93 // The LSB indicates whether shutdown has started. The other bits count the
94 // number of tasks blocking shutdown.
95 subtle::Atomic32 bits_ = 0;
96
97 DISALLOW_COPY_AND_ASSIGN(State);
98 };
99
100 TaskTracker::TaskTracker() : state_(new State) {}
40 TaskTracker::~TaskTracker() = default; 101 TaskTracker::~TaskTracker() = default;
41 102
42 void TaskTracker::Shutdown() { 103 void TaskTracker::Shutdown() {
43 AutoSchedulerLock auto_lock(lock_); 104 AutoSchedulerLock auto_lock(lock_);
44 105
45 // This method should only be called once. 106 // This method should only be called once.
46 DCHECK(!shutdown_completed_); 107 DCHECK(!shutdown_completed_);
47 DCHECK(!shutdown_cv_); 108 DCHECK(!shutdown_cv_);
109 DCHECK(!num_block_shutdown_tasks_posted_during_shutdown_);
110
111 state_->StartShutdown();
112
113 // From now, it a thread causes the number of tasks blocking shutdown to
114 // become zero, it will call OnNumTasksBlockingShutdownIsZero().
48 115
49 shutdown_cv_ = lock_.CreateConditionVariable(); 116 shutdown_cv_ = lock_.CreateConditionVariable();
50 117
51 // Wait until the number of tasks blocking shutdown is zero. 118 // Wait until the number of tasks blocking shutdown is zero.
52 while (num_tasks_blocking_shutdown_ != 0) 119 while (state_->TasksAreBlockingShutdown())
53 shutdown_cv_->Wait(); 120 shutdown_cv_->Wait();
54 121
55 shutdown_cv_.reset(); 122 shutdown_cv_.reset();
56 shutdown_completed_ = true; 123 shutdown_completed_ = true;
57 124
58 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown if less 125 // Record TaskScheduler.BlockShutdownTasksPostedDuringShutdown if less than
59 // than |kMaxBlockShutdownTasksPostedDuringShutdown| BLOCK_SHUTDOWN tasks were 126 // |kMaxBlockShutdownTasksPostedDuringShutdown| BLOCK_SHUTDOWN tasks were
60 // posted during shutdown. Otherwise, the histogram has already been recorded 127 // posted during shutdown. Otherwise, the histogram has already been recorded
61 // in BeforePostTask(). 128 // in BeforePostTask().
62 if (num_block_shutdown_tasks_posted_during_shutdown_ < 129 if (num_block_shutdown_tasks_posted_during_shutdown_ <
63 kMaxBlockShutdownTasksPostedDuringShutdown) { 130 kMaxBlockShutdownTasksPostedDuringShutdown) {
64 RecordNumBlockShutdownTasksPostedDuringShutdown( 131 RecordNumBlockShutdownTasksPostedDuringShutdown(
65 num_block_shutdown_tasks_posted_during_shutdown_); 132 num_block_shutdown_tasks_posted_during_shutdown_);
66 } 133 }
67 } 134 }
68 135
69 bool TaskTracker::WillPostTask(const Task* task) { 136 bool TaskTracker::WillPostTask(const Task* task) {
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 182
116 AfterRunTask(shutdown_behavior); 183 AfterRunTask(shutdown_behavior);
117 } 184 }
118 185
119 bool TaskTracker::IsShuttingDownForTesting() const { 186 bool TaskTracker::IsShuttingDownForTesting() const {
120 AutoSchedulerLock auto_lock(lock_); 187 AutoSchedulerLock auto_lock(lock_);
121 return !!shutdown_cv_; 188 return !!shutdown_cv_;
122 } 189 }
123 190
124 bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) { 191 bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
125 AutoSchedulerLock auto_lock(lock_);
126
127 if (shutdown_completed_) {
128 // A BLOCK_SHUTDOWN task posted after shutdown has completed is an ordering
129 // bug. This DCHECK aims to catch those early.
130 DCHECK_NE(shutdown_behavior, TaskShutdownBehavior::BLOCK_SHUTDOWN);
131
132 // No task is allowed to be posted after shutdown.
133 return false;
134 }
135
136 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) { 192 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
137 // BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted 193 // BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted
138 // and the moment they complete their execution. 194 // and the moment they complete their execution.
139 ++num_tasks_blocking_shutdown_; 195 const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
140 196
141 if (shutdown_cv_) { 197 if (shutdown_started) {
198 AutoSchedulerLock auto_lock(lock_);
199
200 // A BLOCK_SHUTDOWN task posted after shutdown has completed is an
201 // ordering bug. This DCHECK aims to catch those early.
202 DCHECK(!shutdown_completed_);
203
142 ++num_block_shutdown_tasks_posted_during_shutdown_; 204 ++num_block_shutdown_tasks_posted_during_shutdown_;
143 205
144 if (num_block_shutdown_tasks_posted_during_shutdown_ == 206 if (num_block_shutdown_tasks_posted_during_shutdown_ ==
145 kMaxBlockShutdownTasksPostedDuringShutdown) { 207 kMaxBlockShutdownTasksPostedDuringShutdown) {
146 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown 208 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown
147 // histogram as soon as its upper bound is hit. That way, a value will 209 // histogram as soon as its upper bound is hit. That way, a value will
148 // be recorded even if an infinite number of BLOCK_SHUTDOWN tasks are 210 // be recorded even if an infinite number of BLOCK_SHUTDOWN tasks are
149 // posted, preventing shutdown to complete. 211 // posted, preventing shutdown to complete.
150 RecordNumBlockShutdownTasksPostedDuringShutdown( 212 RecordNumBlockShutdownTasksPostedDuringShutdown(
151 num_block_shutdown_tasks_posted_during_shutdown_); 213 num_block_shutdown_tasks_posted_during_shutdown_);
152 } 214 }
153 } 215 }
154 216
155 // A BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
156 // completed.
157 return true; 217 return true;
158 } 218 }
159 219
160 // A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't 220 // A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
161 // started. 221 // started.
162 return !shutdown_cv_; 222 return !state_->ShutdownHasStarted();
163 } 223 }
164 224
165 bool TaskTracker::BeforeRunTask(TaskShutdownBehavior shutdown_behavior) { 225 bool TaskTracker::BeforeRunTask(TaskShutdownBehavior shutdown_behavior) {
166 AutoSchedulerLock auto_lock(lock_); 226 switch (shutdown_behavior) {
227 case TaskShutdownBehavior::BLOCK_SHUTDOWN: {
228 // The number of tasks blocking shutdown has been incremented when the
229 // task was posted.
230 DCHECK(state_->TasksAreBlockingShutdown());
167 231
168 if (shutdown_completed_) { 232 // Trying to run a BLOCK_SHUTDOWN task after shutdown has completed is
169 // Trying to run a BLOCK_SHUTDOWN task after shutdown has completed is 233 // unexpected as it either shouldn't have been posted if shutdown
170 // unexpected as it either shouldn't have been posted if shutdown completed 234 // completed or should be blocking shutdown if it was posted before it
171 // or should be blocking shutdown if it was posted before it did. 235 // did.
172 DCHECK_NE(shutdown_behavior, TaskShutdownBehavior::BLOCK_SHUTDOWN); 236 DCHECK(!state_->ShutdownHasStarted() || !shutdown_completed());
173 237
174 // A WorkerThread might extract a non BLOCK_SHUTDOWN task from a 238 return true;
175 // PriorityQueue after shutdown. It shouldn't be allowed to run it. 239 }
176 return false;
177 }
178 240
179 switch (shutdown_behavior) { 241 case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: {
180 case TaskShutdownBehavior::BLOCK_SHUTDOWN: 242 // SKIP_ON_SHUTDOWN tasks block shutdown while they are running.
181 DCHECK_GT(num_tasks_blocking_shutdown_, 0U); 243 const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
244
245 if (shutdown_started) {
246 // The SKIP_ON_SHUTDOWN task isn't allowed to run during shutdown.
247 // Decrement the number of tasks blocking shutdown that was wrongly
248 // incremented.
249 const bool shutdown_started_and_no_tasks_block_shutdown =
250 state_->DecrementNumTasksBlockingShutdown();
251 if (shutdown_started_and_no_tasks_block_shutdown)
252 OnNumTasksBlockingShutdownIsZero();
253
254 return false;
255 }
256
182 return true; 257 return true;
258 }
183 259
184 case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: 260 case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN: {
185 if (shutdown_cv_) 261 return !state_->ShutdownHasStarted();
186 return false; 262 }
187
188 // SKIP_ON_SHUTDOWN tasks block shutdown while they are running.
189 ++num_tasks_blocking_shutdown_;
190 return true;
191
192 case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN:
193 return !shutdown_cv_;
194 } 263 }
195 264
196 NOTREACHED(); 265 NOTREACHED();
197 return false; 266 return false;
198 } 267 }
199 268
200 void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) { 269 void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) {
201 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN || 270 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN ||
202 shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) { 271 shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) {
203 AutoSchedulerLock auto_lock(lock_); 272 const bool shutdown_started_and_no_tasks_block_shutdown =
204 DCHECK_GT(num_tasks_blocking_shutdown_, 0U); 273 state_->DecrementNumTasksBlockingShutdown();
205 --num_tasks_blocking_shutdown_; 274 if (shutdown_started_and_no_tasks_block_shutdown)
206 if (num_tasks_blocking_shutdown_ == 0 && shutdown_cv_) 275 OnNumTasksBlockingShutdownIsZero();
207 shutdown_cv_->Signal();
208 } 276 }
209 } 277 }
210 278
279 void TaskTracker::OnNumTasksBlockingShutdownIsZero() {
280 AutoSchedulerLock auto_lock(lock_);
281
282 // This method can only be called after shutdown has started.
283 DCHECK(state_->ShutdownHasStarted());
284
285 // The condition below must be true because |lock_| is held:
286 // - Between the time |state_|->StartShutdown() is called and the time
287 // |shutdown_cv_| is instantiated.
288 // - Between the time |shutdown_cv_| is deleted and the time
289 // |shutdown_completed_| is set.
290 DCHECK(!!shutdown_cv_ || shutdown_completed_);
291
292 if (!!shutdown_cv_)
293 shutdown_cv_->Signal();
294 }
295
211 } // namespace internal 296 } // namespace internal
212 } // namespace base 297 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698