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

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: CR danakj #45-46 Created 4 years, 5 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/task_scheduler/task_tracker.h ('k') | base/task_scheduler/task_tracker_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 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 <limits>
8
9 #include "base/atomicops.h"
7 #include "base/callback.h" 10 #include "base/callback.h"
8 #include "base/debug/task_annotator.h" 11 #include "base/debug/task_annotator.h"
12 #include "base/logging.h"
9 #include "base/metrics/histogram_macros.h" 13 #include "base/metrics/histogram_macros.h"
10 #include "base/threading/sequenced_task_runner_handle.h" 14 #include "base/threading/sequenced_task_runner_handle.h"
11 #include "base/threading/thread_restrictions.h" 15 #include "base/threading/thread_restrictions.h"
12 #include "base/threading/thread_task_runner_handle.h" 16 #include "base/threading/thread_task_runner_handle.h"
13 #include "base/trace_event/trace_event.h" 17 #include "base/trace_event/trace_event.h"
14 18
15 namespace base { 19 namespace base {
16 namespace internal { 20 namespace internal {
17 21
18 namespace { 22 namespace {
(...skipping 10 matching lines...) Expand all
29 33
30 void RecordNumBlockShutdownTasksPostedDuringShutdown( 34 void RecordNumBlockShutdownTasksPostedDuringShutdown(
31 HistogramBase::Sample value) { 35 HistogramBase::Sample value) {
32 UMA_HISTOGRAM_CUSTOM_COUNTS( 36 UMA_HISTOGRAM_CUSTOM_COUNTS(
33 "TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1, 37 "TaskScheduler.BlockShutdownTasksPostedDuringShutdown", value, 1,
34 kMaxBlockShutdownTasksPostedDuringShutdown, 50); 38 kMaxBlockShutdownTasksPostedDuringShutdown, 50);
35 } 39 }
36 40
37 } // namespace 41 } // namespace
38 42
39 TaskTracker::TaskTracker() = default; 43 class TaskTracker::State {
44 public:
45 State() = default;
46
47 // Sets a flag indicating that shutdown has started. Returns true if there are
48 // tasks blocking shutdown. Can only be called once.
49 bool StartShutdown() {
50 const auto new_value =
51 subtle::NoBarrier_AtomicIncrement(&bits_, kShutdownHasStartedMask);
52
53 // Check that the "shutdown has started" bit isn't zero. This would happen
54 // if it was incremented twice.
55 DCHECK(new_value & kShutdownHasStartedMask);
56
57 const auto num_tasks_blocking_shutdown =
58 new_value >> kNumTasksBlockingShutdownBitOffset;
59 return num_tasks_blocking_shutdown != 0;
60 }
61
62 // Returns true if shutdown has started.
63 bool HasShutdownStarted() const {
64 return subtle::NoBarrier_Load(&bits_) & kShutdownHasStartedMask;
65 }
66
67 // Returns true if there are tasks blocking shutdown.
68 bool AreTasksBlockingShutdown() const {
69 const auto num_tasks_blocking_shutdown =
70 subtle::NoBarrier_Load(&bits_) >> kNumTasksBlockingShutdownBitOffset;
71 DCHECK_GE(num_tasks_blocking_shutdown, 0);
72 return num_tasks_blocking_shutdown != 0;
73 }
74
75 // Increments the number of tasks blocking shutdown. Returns true if shutdown
76 // has started.
77 bool IncrementNumTasksBlockingShutdown() {
78 #if DCHECK_IS_ON()
79 // Verify that no overflow will occur.
80 const auto num_tasks_blocking_shutdown =
81 subtle::NoBarrier_Load(&bits_) >> kNumTasksBlockingShutdownBitOffset;
82 DCHECK_LT(num_tasks_blocking_shutdown,
danakj 2016/07/08 21:21:29 So I'm still a bit unsure why this is a DCHECK ins
fdoray 2016/07/18 19:01:15 Having 2^30 pending tasks requires more than 2^36
83 std::numeric_limits<subtle::Atomic32>::max() -
84 kNumTasksBlockingShutdownIncrement);
85 #endif
86
87 const auto new_bits = subtle::NoBarrier_AtomicIncrement(
88 &bits_, kNumTasksBlockingShutdownIncrement);
89 return new_bits & kShutdownHasStartedMask;
90 }
91
92 // Decrements the number of tasks blocking shutdown. Returns true if shutdown
93 // has started and the number of tasks blocking shutdown becomes zero.
94 bool DecrementNumTasksBlockingShutdown() {
95 const auto new_bits = subtle::NoBarrier_AtomicIncrement(
96 &bits_, -kNumTasksBlockingShutdownIncrement);
97 const bool shutdown_has_started = new_bits & kShutdownHasStartedMask;
98 const auto num_tasks_blocking_shutdown =
99 new_bits >> kNumTasksBlockingShutdownBitOffset;
100 DCHECK_GE(num_tasks_blocking_shutdown, 0);
101 return shutdown_has_started && num_tasks_blocking_shutdown == 0;
102 }
103
104 private:
105 static constexpr subtle::Atomic32 kShutdownHasStartedMask = 1;
106 static constexpr subtle::Atomic32 kNumTasksBlockingShutdownBitOffset = 1;
107 static constexpr subtle::Atomic32 kNumTasksBlockingShutdownIncrement =
108 1 << kNumTasksBlockingShutdownBitOffset;
109
110 // The LSB indicates whether shutdown has started. The other bits count the
111 // number of tasks blocking shutdown.
112 //
113 // This atomic variable is always read/written without a barrier. This is
114 // correct because it encapsulates all shutdown state. Barrier semantics would
danakj 2016/07/08 21:21:29 I spent some time learning about atomics yesterday
gab 2016/07/13 18:49:10 Quoting Francois from way further up in this revie
danakj 2016/07/13 22:39:17 That's not my understanding at all. The atomic ope
fdoray 2016/07/18 19:01:15 According to your link, atomic operations without
gab 2016/07/19 16:22:20 Now that we've changed to use barriers, it would b
115 // need to be reassessed if another variable was added.
116 subtle::Atomic32 bits_ = 0;
117
118 DISALLOW_COPY_AND_ASSIGN(State);
119 };
120
121 TaskTracker::TaskTracker() : state_(new State) {}
40 TaskTracker::~TaskTracker() = default; 122 TaskTracker::~TaskTracker() = default;
41 123
42 void TaskTracker::Shutdown() { 124 void TaskTracker::Shutdown() {
43 AutoSchedulerLock auto_lock(lock_); 125 {
126 AutoSchedulerLock auto_lock(shutdown_lock_);
44 127
45 // This method should only be called once. 128 // This method can only be called once.
46 DCHECK(!shutdown_completed_); 129 DCHECK(!shutdown_event_);
47 DCHECK(!shutdown_cv_); 130 DCHECK(!num_block_shutdown_tasks_posted_during_shutdown_);
48 131
49 shutdown_cv_ = lock_.CreateConditionVariable(); 132 shutdown_event_.reset(
133 new WaitableEvent(WaitableEvent::ResetPolicy::MANUAL,
134 WaitableEvent::InitialState::NOT_SIGNALED));
50 135
51 // Wait until the number of tasks blocking shutdown is zero. 136 const bool tasks_are_blocking_shutdown = state_->StartShutdown();
52 while (num_tasks_blocking_shutdown_ != 0)
53 shutdown_cv_->Wait();
54 137
55 shutdown_cv_.reset(); 138 // From now, if a thread causes the number of tasks blocking shutdown to
56 shutdown_completed_ = true; 139 // become zero, it will call OnBlockingShutdownTasksComplete().
57 140
58 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown if less 141 if (!tasks_are_blocking_shutdown) {
59 // than |kMaxBlockShutdownTasksPostedDuringShutdown| BLOCK_SHUTDOWN tasks were 142 // If another thread posts a BLOCK_SHUTDOWN task at this moment, it will
60 // posted during shutdown. Otherwise, the histogram has already been recorded 143 // block until this method releases |shutdown_lock_|. Then, it will fail
61 // in BeforePostTask(). 144 // DCHECK(!shutdown_event_->IsSignaled()). This is the desired behavior
62 if (num_block_shutdown_tasks_posted_during_shutdown_ < 145 // because posting a BLOCK_SHUTDOWN task when TaskTracker::Shutdown() has
63 kMaxBlockShutdownTasksPostedDuringShutdown) { 146 // started and no tasks are blocking shutdown isn't allowed.
64 RecordNumBlockShutdownTasksPostedDuringShutdown( 147 shutdown_event_->Signal();
65 num_block_shutdown_tasks_posted_during_shutdown_); 148 return;
149 }
150 }
151
152 // It is safe to access |shutdown_event_| without holding |lock_| because the
153 // pointer never changes after being set above.
154 shutdown_event_->Wait();
155
156 {
157 AutoSchedulerLock auto_lock(shutdown_lock_);
158
159 // Record TaskScheduler.BlockShutdownTasksPostedDuringShutdown if less than
160 // |kMaxBlockShutdownTasksPostedDuringShutdown| BLOCK_SHUTDOWN tasks were
161 // posted during shutdown. Otherwise, the histogram has already been
162 // recorded in BeforePostTask().
163 if (num_block_shutdown_tasks_posted_during_shutdown_ <
164 kMaxBlockShutdownTasksPostedDuringShutdown) {
165 RecordNumBlockShutdownTasksPostedDuringShutdown(
166 num_block_shutdown_tasks_posted_during_shutdown_);
167 }
66 } 168 }
67 } 169 }
68 170
69 bool TaskTracker::WillPostTask(const Task* task) { 171 bool TaskTracker::WillPostTask(const Task* task) {
70 DCHECK(task); 172 DCHECK(task);
71 173
72 if (!BeforePostTask(task->traits.shutdown_behavior())) 174 if (!BeforePostTask(task->traits.shutdown_behavior()))
73 return false; 175 return false;
74 176
75 debug::TaskAnnotator task_annotator; 177 debug::TaskAnnotator task_annotator;
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 211
110 TRACE_TASK_EXECUTION(kRunFunctionName, *task); 212 TRACE_TASK_EXECUTION(kRunFunctionName, *task);
111 213
112 debug::TaskAnnotator task_annotator; 214 debug::TaskAnnotator task_annotator;
113 task_annotator.RunTask(kQueueFunctionName, *task); 215 task_annotator.RunTask(kQueueFunctionName, *task);
114 } 216 }
115 217
116 AfterRunTask(shutdown_behavior); 218 AfterRunTask(shutdown_behavior);
117 } 219 }
118 220
221 bool TaskTracker::IsShutdownComplete() const {
222 AutoSchedulerLock auto_lock(shutdown_lock_);
223 return shutdown_event_ && shutdown_event_->IsSignaled();
224 }
225
119 bool TaskTracker::IsShuttingDownForTesting() const { 226 bool TaskTracker::IsShuttingDownForTesting() const {
120 AutoSchedulerLock auto_lock(lock_); 227 AutoSchedulerLock auto_lock(shutdown_lock_);
121 return !!shutdown_cv_; 228 return shutdown_event_ && !shutdown_event_->IsSignaled();
122 } 229 }
123 230
124 bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) { 231 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) { 232 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN) {
137 // BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted 233 // BLOCK_SHUTDOWN tasks block shutdown between the moment they are posted
138 // and the moment they complete their execution. 234 // and the moment they complete their execution.
139 ++num_tasks_blocking_shutdown_; 235 const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
140 236
141 if (shutdown_cv_) { 237 if (shutdown_started) {
238 AutoSchedulerLock auto_lock(shutdown_lock_);
239
240 // A BLOCK_SHUTDOWN task posted after shutdown has completed is an
241 // ordering bug. This aims to catch those early.
242 DCHECK(shutdown_event_);
243 DCHECK(!shutdown_event_->IsSignaled());
244
142 ++num_block_shutdown_tasks_posted_during_shutdown_; 245 ++num_block_shutdown_tasks_posted_during_shutdown_;
143 246
144 if (num_block_shutdown_tasks_posted_during_shutdown_ == 247 if (num_block_shutdown_tasks_posted_during_shutdown_ ==
145 kMaxBlockShutdownTasksPostedDuringShutdown) { 248 kMaxBlockShutdownTasksPostedDuringShutdown) {
146 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown 249 // Record the TaskScheduler.BlockShutdownTasksPostedDuringShutdown
147 // histogram as soon as its upper bound is hit. That way, a value will 250 // 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 251 // be recorded even if an infinite number of BLOCK_SHUTDOWN tasks are
149 // posted, preventing shutdown to complete. 252 // posted, preventing shutdown to complete.
150 RecordNumBlockShutdownTasksPostedDuringShutdown( 253 RecordNumBlockShutdownTasksPostedDuringShutdown(
151 num_block_shutdown_tasks_posted_during_shutdown_); 254 num_block_shutdown_tasks_posted_during_shutdown_);
152 } 255 }
153 } 256 }
154 257
155 // A BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
156 // completed.
157 return true; 258 return true;
158 } 259 }
159 260
160 // A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't 261 // A non BLOCK_SHUTDOWN task is allowed to be posted iff shutdown hasn't
161 // started. 262 // started.
162 return !shutdown_cv_; 263 return !state_->HasShutdownStarted();
163 } 264 }
164 265
165 bool TaskTracker::BeforeRunTask(TaskShutdownBehavior shutdown_behavior) { 266 bool TaskTracker::BeforeRunTask(TaskShutdownBehavior shutdown_behavior) {
166 AutoSchedulerLock auto_lock(lock_); 267 switch (shutdown_behavior) {
268 case TaskShutdownBehavior::BLOCK_SHUTDOWN: {
269 // The number of tasks blocking shutdown has been incremented when the
270 // task was posted.
271 DCHECK(state_->AreTasksBlockingShutdown());
167 272
168 if (shutdown_completed_) { 273 // 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 274 // unexpected as it either shouldn't have been posted if shutdown
170 // unexpected as it either shouldn't have been posted if shutdown completed 275 // 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. 276 // did.
172 DCHECK_NE(shutdown_behavior, TaskShutdownBehavior::BLOCK_SHUTDOWN); 277 DCHECK(!state_->HasShutdownStarted() || !IsShutdownComplete());
173 278
174 // A worker might extract a non BLOCK_SHUTDOWN task from a PriorityQueue 279 return true;
175 // after shutdown. It shouldn't be allowed to run it. 280 }
176 return false;
177 }
178 281
179 switch (shutdown_behavior) { 282 case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: {
180 case TaskShutdownBehavior::BLOCK_SHUTDOWN: 283 // SKIP_ON_SHUTDOWN tasks block shutdown while they are running.
181 DCHECK_GT(num_tasks_blocking_shutdown_, 0U); 284 const bool shutdown_started = state_->IncrementNumTasksBlockingShutdown();
285
286 if (shutdown_started) {
287 // The SKIP_ON_SHUTDOWN task isn't allowed to run during shutdown.
288 // Decrement the number of tasks blocking shutdown that was wrongly
289 // incremented.
290 const bool shutdown_started_and_no_tasks_block_shutdown =
291 state_->DecrementNumTasksBlockingShutdown();
292 if (shutdown_started_and_no_tasks_block_shutdown)
293 OnBlockingShutdownTasksComplete();
294
295 return false;
296 }
297
182 return true; 298 return true;
299 }
183 300
184 case TaskShutdownBehavior::SKIP_ON_SHUTDOWN: 301 case TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN: {
185 if (shutdown_cv_) 302 return !state_->HasShutdownStarted();
186 return false; 303 }
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 } 304 }
195 305
196 NOTREACHED(); 306 NOTREACHED();
197 return false; 307 return false;
198 } 308 }
199 309
200 void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) { 310 void TaskTracker::AfterRunTask(TaskShutdownBehavior shutdown_behavior) {
201 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN || 311 if (shutdown_behavior == TaskShutdownBehavior::BLOCK_SHUTDOWN ||
202 shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) { 312 shutdown_behavior == TaskShutdownBehavior::SKIP_ON_SHUTDOWN) {
203 AutoSchedulerLock auto_lock(lock_); 313 const bool shutdown_started_and_no_tasks_block_shutdown =
204 DCHECK_GT(num_tasks_blocking_shutdown_, 0U); 314 state_->DecrementNumTasksBlockingShutdown();
205 --num_tasks_blocking_shutdown_; 315 if (shutdown_started_and_no_tasks_block_shutdown)
206 if (num_tasks_blocking_shutdown_ == 0 && shutdown_cv_) 316 OnBlockingShutdownTasksComplete();
207 shutdown_cv_->Signal();
208 } 317 }
209 } 318 }
210 319
320 void TaskTracker::OnBlockingShutdownTasksComplete() {
321 AutoSchedulerLock auto_lock(shutdown_lock_);
322
323 // This method can only be called after shutdown has started.
324 DCHECK(state_->HasShutdownStarted());
325 DCHECK(shutdown_event_);
326
327 shutdown_event_->Signal();
328 }
329
211 } // namespace internal 330 } // namespace internal
212 } // namespace base 331 } // namespace base
OLDNEW
« no previous file with comments | « base/task_scheduler/task_tracker.h ('k') | base/task_scheduler/task_tracker_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698